diff -r 3d4e9c994f10 -r a86126ab1dd4 wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php --- a/wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php Tue Oct 22 16:11:46 2019 +0200 +++ b/wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php Tue Dec 15 13:49:49 2020 +0100 @@ -58,11 +58,21 @@ * @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 ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.' ), array( 'status' => rest_authorization_required_code() ) ); + if ( current_user_can( 'edit_posts' ) ) { + return true; } - return true; + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { + if ( current_user_can( $post_type->cap->edit_posts ) ) { + return true; + } + } + + return new WP_Error( + 'rest_user_cannot_view', + __( 'Sorry, you are not allowed to view themes.' ), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -105,20 +115,89 @@ $data = array(); $fields = $this->get_fields_for_response( $request ); - if ( in_array( 'theme_supports', $fields, true ) ) { - $formats = get_theme_support( 'post-formats' ); - $formats = is_array( $formats ) ? array_values( $formats[0] ) : array(); - $formats = array_merge( array( 'standard' ), $formats ); - $data['theme_supports']['formats'] = $formats; + if ( rest_is_field_included( 'stylesheet', $fields ) ) { + $data['stylesheet'] = $theme->get_stylesheet(); + } + + if ( rest_is_field_included( 'template', $fields ) ) { + /** + * Use the get_template() method, not the 'Template' header, for finding the template. + * The 'Template' header is only good for what was written in the style.css, while + * get_template() takes into account where WordPress actually located the theme and + * whether it is actually valid. + */ + $data['template'] = $theme->get_template(); + } + + $plain_field_mappings = array( + 'requires_php' => 'RequiresPHP', + 'requires_wp' => 'RequiresWP', + 'textdomain' => 'TextDomain', + 'version' => 'Version', + ); + + foreach ( $plain_field_mappings as $field => $header ) { + if ( rest_is_field_included( $field, $fields ) ) { + $data[ $field ] = $theme->get( $header ); + } + } + + if ( rest_is_field_included( 'screenshot', $fields ) ) { + // Using $theme->get_screenshot() with no args to get absolute URL. + $data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : ''; + } + + $rich_field_mappings = array( + 'author' => 'Author', + 'author_uri' => 'AuthorURI', + 'description' => 'Description', + 'name' => 'Name', + 'tags' => 'Tags', + 'theme_uri' => 'ThemeURI', + ); - $data['theme_supports']['post-thumbnails'] = false; - $data['theme_supports']['responsive-embeds'] = (bool) get_theme_support( 'responsive-embeds' ); - $post_thumbnails = get_theme_support( 'post-thumbnails' ); + foreach ( $rich_field_mappings as $field => $header ) { + if ( rest_is_field_included( "{$field}.raw", $fields ) ) { + $data[ $field ]['raw'] = $theme->display( $header, false, true ); + } + + if ( rest_is_field_included( "{$field}.rendered", $fields ) ) { + $data[ $field ]['rendered'] = $theme->display( $header ); + } + } + + if ( rest_is_field_included( 'theme_supports', $fields ) ) { + foreach ( get_registered_theme_features() as $feature => $config ) { + if ( ! is_array( $config['show_in_rest'] ) ) { + continue; + } + + $name = $config['show_in_rest']['name']; - if ( $post_thumbnails ) { - // $post_thumbnails can contain a nested array of post types. - // e.g. array( array( 'post', 'page' ) ). - $data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true; + if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) { + continue; + } + + if ( ! current_theme_supports( $feature ) ) { + $data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default']; + continue; + } + + $support = get_theme_support( $feature ); + + if ( isset( $config['show_in_rest']['prepare_callback'] ) ) { + $prepare = $config['show_in_rest']['prepare_callback']; + } else { + $prepare = array( $this, 'prepare_theme_support' ); + } + + $prepared = $prepare( $support, $config, $feature, $request ); + + if ( is_wp_error( $prepared ) ) { + continue; + } + + $data['theme_supports'][ $name ] = $prepared; } } @@ -140,6 +219,31 @@ } /** + * Prepares the theme support value for inclusion in the REST API response. + * + * @since 5.5.0 + * + * @param mixed $support The raw value from get_theme_support(). + * @param array $args The feature's registration args. + * @param string $feature The feature name. + * @param WP_REST_Request $request The request object. + * @return mixed The prepared support value. + */ + protected function prepare_theme_support( $support, $args, $feature, $request ) { + $schema = $args['show_in_rest']['schema']; + + if ( 'boolean' === $schema['type'] ) { + return true; + } + + if ( is_array( $support ) && ! $args['variadic'] ) { + $support = $support[0]; + } + + return rest_sanitize_value_from_schema( $support, $schema ); + } + + /** * Retrieves the theme's schema, conforming to JSON Schema. * * @since 5.0.0 @@ -147,37 +251,170 @@ * @return array Item schema data. */ public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'theme', 'type' => 'object', 'properties' => array( - 'theme_supports' => array( - 'description' => __( 'Features supported by this theme.' ), - 'type' => 'array', + 'stylesheet' => array( + 'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'template' => array( + 'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'author' => array( + 'description' => __( 'The theme author.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme author\'s name, as found in the theme header.' ), + 'type' => 'string', + ), + 'rendered' => array( + 'description' => __( 'HTML for the theme author, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'author_uri' => array( + 'description' => __( 'The website of the theme author.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The website of the theme author, as found in the theme header.' ), + 'type' => 'string', + 'format' => 'uri', + ), + 'rendered' => array( + 'description' => __( 'The website of the theme author, transformed for display.' ), + 'type' => 'string', + 'format' => 'uri', + ), + ), + ), + 'description' => array( + 'description' => __( 'A description of the theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme description, as found in the theme header.' ), + 'type' => 'string', + ), + 'rendered' => array( + 'description' => __( 'The theme description, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'name' => array( + 'description' => __( 'The name of the theme.' ), + 'type' => 'object', 'readonly' => true, 'properties' => array( - 'formats' => array( - 'description' => __( 'Post formats supported.' ), - 'type' => 'array', - 'readonly' => true, + 'raw' => array( + 'description' => __( 'The theme name, as found in the theme header.' ), + 'type' => 'string', ), - 'post-thumbnails' => array( - 'description' => __( 'Whether the theme supports post thumbnails.' ), - 'type' => array( 'array', 'bool' ), - 'readonly' => true, - ), - 'responsive-embeds' => array( - 'description' => __( 'Whether the theme supports responsive embedded content.' ), - 'type' => 'bool', - 'readonly' => true, + 'rendered' => array( + 'description' => __( 'The theme name, transformed for display.' ), + 'type' => 'string', ), ), ), + 'requires_php' => array( + 'description' => __( 'The minimum PHP version required for the theme to work.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'requires_wp' => array( + 'description' => __( 'The minimum WordPress version required for the theme to work.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'screenshot' => array( + 'description' => __( 'The theme\'s screenshot URL.' ), + 'type' => 'string', + 'format' => 'uri', + 'readonly' => true, + ), + 'tags' => array( + 'description' => __( 'Tags indicating styles and features of the theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme tags, as found in the theme header.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'rendered' => array( + 'description' => __( 'The theme tags, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'textdomain' => array( + 'description' => __( 'The theme\'s text domain.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'theme_supports' => array( + 'description' => __( 'Features supported by this theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array(), + ), + 'theme_uri' => array( + 'description' => __( 'The URI of the theme\'s webpage.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ), + 'type' => 'string', + 'format' => 'uri', + ), + 'rendered' => array( + 'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ), + 'type' => 'string', + 'format' => 'uri', + ), + ), + ), + 'version' => array( + 'description' => __( 'The theme\'s current version.' ), + 'type' => 'string', + 'readonly' => true, + ), ), ); - return $this->add_additional_fields_schema( $schema ); + foreach ( get_registered_theme_features() as $feature => $config ) { + if ( ! is_array( $config['show_in_rest'] ) ) { + continue; + } + + $name = $config['show_in_rest']['name']; + + $schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema']; + } + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); } /** @@ -206,7 +443,7 @@ * * @since 5.0.0 * - * @param array $query_params JSON Schema-formatted collection parameters. + * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_themes_collection_params', $query_params ); } @@ -216,9 +453,9 @@ * * @since 5.0.0 * - * @param string|array $statuses One or more theme statuses. - * @param WP_REST_Request $request Full details about the request. - * @param string $parameter Additional parameter to pass to validation. + * @param string|array $statuses One or more theme statuses. + * @param WP_REST_Request $request Full details about the request. + * @param string $parameter Additional parameter to pass to validation. * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. */ public function sanitize_theme_status( $statuses, $request, $parameter ) {