Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose theme_supports data through a minimial theme controller #10518

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
049f30f
First pass at adding a Themes endpoint for the current active theme.
desrosj Oct 10, 2018
9187617
Don't return `false` when the theme does not support thumbnails. Only…
desrosj Oct 11, 2018
e013653
Use the new active theme endpoint for determining theme support.
desrosj Oct 11, 2018
08eb03d
Add unit tests for the new active theme controller.
desrosj Oct 11, 2018
5b5675d
Pre-load the active theme endpoint.
desrosj Oct 11, 2018
3af33f2
PR Review Feedback adjustments:
desrosj Oct 11, 2018
f18a663
Fix failing tests. Change default context to edit.
desrosj Oct 11, 2018
7a1c75e
Add missing textdomain.
desrosj Oct 11, 2018
06fb8cf
Remove contexts from themes (see https://wordpress.slack.com/archives…
desrosj Oct 11, 2018
2ff2fb4
Merge branch 'master' of github.com:WordPress/gutenberg into add/core…
desrosj Oct 11, 2018
2bbd6ba
Always include `post-thumbnails` property to ensure consistent respon…
desrosj Oct 12, 2018
f593169
Avoid using the `__get()` magic method.
desrosj Oct 12, 2018
6e8abc1
Change the `themes/active` route to `themes?status=active`. The forme…
desrosj Oct 12, 2018
c972f4a
Change variable name from `currentTheme` to `activeThemes`, as the en…
desrosj Oct 12, 2018
d3dbb6f
Remove `_links` from the response and adjust the theme supports prope…
desrosj Oct 15, 2018
9693640
Merge branch 'master' of github.com:WordPress/gutenberg into add/core…
desrosj Oct 15, 2018
b1fe1b7
Updating JavaScript documentation.
desrosj Oct 15, 2018
f7cd0a5
Rename `TotalThemes` to `TotalPages`
danielbachhuber Oct 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions lib/class-wp-rest-themes-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<?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 . '/active',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per discussion in Slack, we should use the following structure for querying this information:

  • /wp/v2/themes?status=active will return an array containing the object representing the capabilities of the active theme.
  • For this initial release, status will be an enum parameter with one option, active, and that parameter will be required.
    • This leaves us room to expand the functionality of the endpoint to permit listing all themes down the road, etc, but restricts the complexity to the bare minimum currently needed.
    • Using a filter further avoids any lock-in around how to point to the active theme from other resources (the debate between a link from the index, /active, /~active, etc, which has unfolded mostly in slack). Any of these approaches might be found to be limiting down the road as we add support for theme management, multi-site, etcetera, while a query parameter like status is unlikely to conflict with any future plans.

Long-term I imagine we'd update the active theme by PUT'ing a theme slug to the settings endpoint, or a hypothetical new site endpoint. Figuring all that out is out of scope and not necessary for Gutenberg in terms of 5.0.

array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'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_item_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() ) );
}

return true;
}

/**
* Retrieves the active theme.
*
* @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_item( $request ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the comment about ?status=active, it would probably be best to introduce a get_items method since we will technically be querying the collection endpoint to return the active theme.

$theme = wp_get_theme();

$response = $this->prepare_item_for_response( $theme, $request );
$response = rest_ensure_response( $response );

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( 'name', $fields, true ) ) {
$data['name'] = $theme->__get( 'name' );
}

if ( in_array( 'stylesheet', $fields, true ) ) {
$data['stylesheet'] = $theme->__get( 'stylesheet' );
}

if ( in_array( 'version', $fields, true ) ) {
$data['version'] = $theme->__get( 'version' );
}

if ( in_array( 'template', $fields, true ) ) {
$data['template'] = $theme->__get( 'template' );
}

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;

$post_thumbnails = get_theme_support( 'post-thumbnails' );

if ( $post_thumbnails ) {
desrosj marked this conversation as resolved.
Show resolved Hide resolved
// $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;
}
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';

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

// 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.
*
* @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 );
}

/**
* Prepares links for the theme request.
*
* @since 5.0.0
*
* @param WP_Theme $theme Theme object.
* @return array Links for the given theme.
*/
protected function prepare_links( $theme ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/active', $this->namespace, $this->rest_base ) ),
),
);

return $links;
}

/**
* 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(
'name' => array(
'description' => __( 'Theme name', 'gutenberg' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'stylesheet' => array(
'description' => __( 'The directory name of the theme.', 'gutenberg' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'version' => array(
'description' => __( 'Theme version', 'gutenberg' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'template' => array(
'description' => __( 'The theme template', 'gutenberg' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'theme_supports' => array(
'description' => __( 'A list of features this theme supports.', 'gutenberg' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Language tweak: Features supported by this theme.

'type' => 'array',
'context' => array( 'edit' ),
'readonly' => true,
'properties' => array(
'formats' => array(
'description' => __( 'Post formats supported.', 'gutenberg' ),
'type' => 'array',
'context' => array( 'edit' ),
'readonly' => true,
),
'post-thumbnails' => array(
'description' => __( 'Whether the theme supports post thumbnails.', 'gutenberg' ),
'type' => array( 'array', 'bool' ),
'context' => array( 'edit' ),
'readonly' => true,
),
),
),
),
);
danielbachhuber marked this conversation as resolved.
Show resolved Hide resolved

return $this->add_additional_fields_schema( $schema );
}
}
1 change: 1 addition & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
'/',
'/wp/v2/types?context=edit',
'/wp/v2/taxonomies?per_page=-1&context=edit',
'/wp/v2/themes/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
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require dirname( __FILE__ ) . '/class-wp-rest-blocks-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-block-renderer-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-themes-controller.php';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to wrap this with a class_exists() but it can happen in a follow-up PR for all of the controllers: #10526

require dirname( __FILE__ ) . '/class-wp-rest-search-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-search-handler.php';
require dirname( __FILE__ ) . '/class-wp-rest-post-search-handler.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 currentTheme = yield apiFetch( { path: '/wp/v2/themes/active' } );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this endpoint to that list. It correctly prevents the Featured Image editor from flashing onto the screen like it was.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, do we need to pass the edit context here? (and the preloaded path)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That I am not sure of. The default context is now edit because that is the only context this endpoint currently supports. But might be good to include it for when/if that default changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context was removed entirely per the discussion in #core-restapi.

yield receiveThemeSupports( currentTheme.theme_supports );
}

/**
Expand Down
41 changes: 0 additions & 41 deletions phpunit/class-gutenberg-rest-api-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,47 +297,6 @@ function test_link_term_management_per_user() {
$this->assertTrue( isset( $links[ $assign_categories ] ) );
}

/**
* Should include relevant data in the 'theme_supports' key of index.
*/
function test_theme_supports_index() {
$request = new WP_REST_Request( 'GET', '/' );
$response = rest_do_request( $request );
$result = $response->get_data();
$this->assertTrue( isset( $result['theme_supports'] ) );
$this->assertTrue( isset( $result['theme_supports']['formats'] ) );
$this->assertTrue( in_array( 'standard', $result['theme_supports']['formats'] ) );
}

public function test_theme_supports_post_thumbnails_false() {
remove_theme_support( 'post-thumbnails' );
$request = new WP_REST_Request( 'GET', '/' );
$response = rest_do_request( $request );
$result = $response->get_data();
$this->assertTrue( isset( $result['theme_supports'] ) );
$this->assertFalse( isset( $result['theme_supports']['post-thumbnails'] ) );
}

public function test_theme_supports_post_thumbnails_true() {
remove_theme_support( 'post-thumbnails' );
add_theme_support( 'post-thumbnails' );
$request = new WP_REST_Request( 'GET', '/' );
$response = rest_do_request( $request );
$result = $response->get_data();
$this->assertTrue( isset( $result['theme_supports'] ) );
$this->assertEquals( true, $result['theme_supports']['post-thumbnails'] );
}

public function test_theme_supports_post_thumbnails_array() {
remove_theme_support( 'post-thumbnails' );
add_theme_support( 'post-thumbnails', array( 'post' ) );
$request = new WP_REST_Request( 'GET', '/' );
$response = rest_do_request( $request );
$result = $response->get_data();
$this->assertTrue( isset( $result['theme_supports'] ) );
$this->assertEquals( array( 'post' ), $result['theme_supports']['post-thumbnails'] );
}

public function test_get_taxonomies_context_edit() {
wp_set_current_user( $this->contributor );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
Expand Down
Loading