Skip to content

Commit

Permalink
Expose theme_supports data through a minimial theme controller (#10518)
Browse files Browse the repository at this point in the history
* First pass at adding a Themes endpoint for the current active theme.

* Don't return `false` when the theme does not support thumbnails. Only list view context on formats and post-thumbnails fields. Uncomment permissions logic. Remove old code placing this data in the index.

* Use the new active theme endpoint for determining theme support.

* Add unit tests for the new active theme controller.

* Pre-load the active theme endpoint.

* PR Review Feedback adjustments:
- Alignment fixes
- Add missing textdomain arguments
- Update the property contexts to edit (should be the context the data is accessed)
- Add missing description for `post-thumbnails`
- Link should be `self`, not `active`

* Fix failing tests. Change default context to edit.

* Add missing textdomain.

* Remove contexts from themes (see https://wordpress.slack.com/archives/C02RQC26G/p1539297122000100).

* Always include `post-thumbnails` property to ensure consistent responses.

* Avoid using the `__get()` magic method.

* Change the `themes/active` route to `themes?status=active`. The former would cause future issues when `themes/themename` returns data for a specific theme.

For now, limit the `status` param to one value, `active`, by specifying the param as an `enum`.

Also, remove field not 100% required for Gutenberg (name, version, template, etc.).

* Change variable name from `currentTheme` to `activeThemes`, as the endpoint now returns a collection.

* Remove `_links` from the response and adjust the theme supports property description.

* Updating JavaScript documentation.

* Rename `TotalThemes` to `TotalPages`
  • Loading branch information
desrosj authored and kadamwhite committed Oct 15, 2018
1 parent 0ea7c43 commit de2fab7
Show file tree
Hide file tree
Showing 9 changed files with 565 additions and 83 deletions.
4 changes: 2 additions & 2 deletions docs/data/data-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ Returns an action object used in signalling that entity records have been receiv
* records: Records received.
* query: Query Object.

### receiveThemeSupportsFromIndex
### receiveThemeSupports

Returns an action object used in signalling that the index has been received.

*Parameters*

* index: Index received.
* themeSupports: Theme support for the current theme.

### receiveEmbedPreview

Expand Down
231 changes: 231 additions & 0 deletions lib/class-wp-rest-themes-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
<?php
/**
* REST API: WP_REST_Themes_Controller class
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/

/**
* Core class used to manage themes via the REST API.
*
* @since 5.0.0
*
* @see WP_REST_Controller
*/
class WP_REST_Themes_Controller extends WP_REST_Controller {

/**
* Constructor.
*
* @since 5.0.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'themes';
}

/**
* Registers the routes for the objects of the controller.
*
* @since 5.0.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
}

/**
* Checks if a given request has access to read the theme.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Full details about the request.
* @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.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
}

return true;
}

/**
* Retrieves a collection of themes.
*
* @since 5.0.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_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
$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 );
}

$response = rest_ensure_response( $themes );

$response->header( 'X-WP-Total', count( $themes ) );
$response->header( 'X-WP-TotalPages', count( $themes ) );

return $response;
}

/**
* Prepares a single theme output for response.
*
* @since 5.0.0
*
* @param WP_Theme $theme Theme object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $theme, $request ) {
$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;

$data['theme_supports']['post-thumbnails'] = false;
$post_thumbnails = get_theme_support( 'post-thumbnails' );

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;
}
}

$data = $this->add_additional_fields_to_object( $data, $request );

// Wrap the data in a response object.
$response = rest_ensure_response( $data );

/**
* Filters theme data returned from the REST API.
*
* @since 5.0.0
*
* @param WP_REST_Response $response The response object.
* @param WP_Theme $theme Theme object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_prepare_theme', $response, $theme, $request );
}

/**
* Retrieves the theme's schema, conforming to JSON Schema.
*
* @since 5.0.0
*
* @return array Item schema data.
*/
public function get_item_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.', 'gutenberg' ),
'type' => 'array',
'readonly' => true,
'properties' => array(
'formats' => array(
'description' => __( 'Post formats supported.', 'gutenberg' ),
'type' => 'array',
'readonly' => true,
),
'post-thumbnails' => array(
'description' => __( 'Whether the theme supports post thumbnails.', 'gutenberg' ),
'type' => array( 'array', 'bool' ),
'readonly' => true,
),
),
),
),
);

return $this->add_additional_fields_schema( $schema );
}

/**
* Retrieves the search params for the themes collection.
*
* @since 5.0.0
*
* @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.', 'gutenberg' ),
'type' => 'array',
'items' => array(
'enum' => array( 'active' ),
'type' => 'string',
),
'required' => true,
'sanitize_callback' => array( $this, 'sanitize_theme_status' ),
);

/**
* Filter collection parameters for the themes controller.
*
* @since 5.0.0
*
* @param array $query_params JSON Schema-formatted collection parameters.
*/
return apply_filters( 'rest_themes_collection_params', $query_params );
}

/**
* Sanitizes and validates the list of theme status.
*
* @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.
* @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
*/
public function sanitize_theme_status( $statuses, $request, $parameter ) {
$statuses = wp_parse_slug_list( $statuses );

foreach ( $statuses as $status ) {
$result = rest_validate_request_arg( $status, $request, $parameter );

if ( is_wp_error( $result ) ) {
return $result;
}
}

return $statuses;
}
}
1 change: 1 addition & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
'/',
'/wp/v2/types?context=edit',
'/wp/v2/taxonomies?per_page=-1&context=edit',
'/wp/v2/themes?status=active',
sprintf( '/wp/v2/%s/%s?context=edit', $rest_base, $post->ID ),
sprintf( '/wp/v2/types/%s?context=edit', $post_type ),
sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ),
Expand Down
4 changes: 4 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
if ( ! class_exists( 'WP_REST_Autosaves_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php';
}
if ( ! class_exists( 'WP_REST_Themes_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-themes-controller.php';
}
if ( ! class_exists( 'WP_REST_Block_Renderer_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-block-renderer-controller.php';
}
Expand All @@ -30,6 +33,7 @@
if ( ! class_exists( 'WP_REST_Post_Search_Handler' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-post-search-handler.php';
}

require dirname( __FILE__ ) . '/rest-api.php';
}

Expand Down
37 changes: 3 additions & 34 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ function gutenberg_register_rest_routes() {
$autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
$autosaves_controller->register_routes();
}

$themes_controller = new WP_REST_Themes_Controller();
$themes_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_routes' );

Expand Down Expand Up @@ -304,40 +307,6 @@ function gutenberg_register_taxonomy_prepare_functions( $taxonomy ) {
}
add_filter( 'registered_taxonomy', 'gutenberg_register_taxonomy_prepare_functions' );

/**
* Ensure that the wp-json index contains the 'theme-supports' setting as
* part of its site info elements.
*
* @see https://core.trac.wordpress.org/ticket/45016
*
* @param WP_REST_Response $response WP REST API response of the wp-json index.
* @return WP_REST_Response Response that contains theme-supports.
*/
function gutenberg_ensure_wp_json_has_theme_supports( $response ) {
$site_info = $response->get_data();
if ( ! array_key_exists( 'theme_supports', $site_info ) ) {
$site_info['theme_supports'] = array();
}
if ( ! array_key_exists( 'formats', $site_info['theme_supports'] ) ) {
$formats = get_theme_support( 'post-formats' );
$formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
$formats = array_merge( array( 'standard' ), $formats );

$site_info['theme_supports']['formats'] = $formats;
}
if ( ! array_key_exists( 'post-thumbnails', $site_info['theme_supports'] ) ) {
$post_thumbnails = get_theme_support( 'post-thumbnails' );
if ( $post_thumbnails ) {
// $post_thumbnails can contain a nested array of post types.
// e.g. array( array( 'post', 'page' ) ).
$site_info['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true;
}
}
$response->set_data( $site_info );
return $response;
}
add_filter( 'rest_index', 'gutenberg_ensure_wp_json_has_theme_supports' );

/**
* Handle any necessary checks early.
*
Expand Down
6 changes: 3 additions & 3 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ export function receiveEntityRecords( kind, name, records, query ) {
/**
* Returns an action object used in signalling that the index has been received.
*
* @param {Object} index Index received.
* @param {Object} themeSupports Theme support for the current theme.
*
* @return {Object} Action object.
*/
export function receiveThemeSupportsFromIndex( index ) {
export function receiveThemeSupports( themeSupports ) {
return {
type: 'RECEIVE_THEME_SUPPORTS',
themeSupports: index.theme_supports,
themeSupports,
};
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { addQueryArgs } from '@wordpress/url';
import {
receiveUserQuery,
receiveEntityRecords,
receiveThemeSupportsFromIndex,
receiveThemeSupports,
receiveEmbedPreview,
} from './actions';
import { getKindEntities } from './entities';
Expand Down Expand Up @@ -70,8 +70,8 @@ export function* getEntityRecords( kind, name, query = {} ) {
* Requests theme supports data from the index.
*/
export function* getThemeSupports() {
const index = yield apiFetch( { path: '/' } );
yield receiveThemeSupportsFromIndex( index );
const activeThemes = yield apiFetch( { path: '/wp/v2/themes?status=active' } );
yield receiveThemeSupports( activeThemes[ 0 ].theme_supports );
}

/**
Expand Down
Loading

0 comments on commit de2fab7

Please sign in to comment.