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

feat: Experimental editor assets REST API endpoint #64517

Open
wants to merge 13 commits into
base: trunk
Choose a base branch
from
Open
193 changes: 193 additions & 0 deletions lib/experimental/class-wp-rest-block-editor-assets-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<?php
/**
* REST API: WP_REST_Block_Editor_Assets_Controller class
*
* @package WordPress
* @subpackage REST_API
*/

if ( ! class_exists( 'WP_REST_Block_Editor_Assets_Controller' ) ) {

/**
* Core class used to retrieve the block editor assets via the REST API.
*
* @see WP_REST_Controller
*/
class WP_REST_Block_Editor_Assets_Controller extends WP_REST_Controller {
/**
* Constructor.
*/
public function __construct() {
$this->namespace = '__experimental/wp-block-editor/v1';
$this->rest_base = 'editor-assets';
}

/**
* Registers the controller routes.
*/
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' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}

/**
* Retrieves a collection of items.
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
global $wp_styles, $wp_scripts;

$current_wp_styles = $wp_styles;
$current_wp_scripts = $wp_scripts;

$wp_styles = new WP_Styles();
$wp_scripts = new WP_Scripts();

// Trigger an action frequently used by plugins to enqueue assets.
do_action( 'wp_loaded' );

// We generally do not need reset styles for the block editor. However, if
// it's a classic theme, margins will be added to every block, which is
// reset specifically for list items, so classic themes rely on these
// reset styles.
$wp_styles->done =
wp_theme_has_theme_json() ? array( 'wp-reset-editor-styles' ) : array();

wp_enqueue_script( 'wp-polyfill' );
// Enqueue the `editorStyle` handles for all core block, and dependencies.
wp_enqueue_style( 'wp-edit-blocks' );

if ( current_theme_supports( 'wp-block-styles' ) ) {
wp_enqueue_style( 'wp-block-library-theme' );
}

// Enqueue frequent dependent, admin-only `dashicon` asset.
wp_enqueue_style( 'dashicons' );

// Enqueue the admin-only `postbox` asset required for the block editor.
$suffix = wp_scripts_get_suffix();
wp_enqueue_script( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable', 'wp-a11y' ), false, 1 );

// Enqueue foundational post editor assets.
wp_enqueue_script( 'wp-edit-post' );
wp_enqueue_style( 'wp-edit-post' );

// Ensure the block editor scripts and styles are enqueued.
add_filter( 'should_load_block_editor_scripts_and_styles', '__return_true' );
do_action( 'enqueue_block_assets' );
do_action( 'enqueue_block_editor_assets' );
remove_filter( 'should_load_block_editor_scripts_and_styles', '__return_true' );

// Additionally, enqueue `editorStyle` and `editorScript` assets for all
// blocks, which contains editor-only styling for blocks (editor content).
$block_registry = WP_Block_Type_Registry::get_instance();
foreach ( $block_registry->get_all_registered() as $block_type ) {
if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) {
foreach ( $block_type->editor_style_handles as $style_handle ) {
wp_enqueue_style( $style_handle );
}
}
if ( isset( $block_type->editor_script_handles ) && is_array( $block_type->editor_script_handles ) ) {
foreach ( $block_type->editor_script_handles as $script_handle ) {
wp_enqueue_script( $script_handle );
}
}
}

// Remove the deprecated `print_emoji_styles` handler. It avoids breaking
// style generation with a deprecation message.
$has_emoji_styles = has_action( 'wp_print_styles', 'print_emoji_styles' );
if ( $has_emoji_styles ) {
remove_action( 'wp_print_styles', 'print_emoji_styles' );
}

ob_start();
wp_print_styles();
$styles = ob_get_clean();

if ( $has_emoji_styles ) {
add_action( 'wp_print_styles', 'print_emoji_styles' );
}

ob_start();
wp_print_head_scripts();
wp_print_footer_scripts();
$scripts = ob_get_clean();

$wp_styles = $current_wp_styles;
$wp_scripts = $current_wp_scripts;

return array(
'styles' => $styles,
'scripts' => $scripts,
);
}

/**
* Checks the permissions for retrieving items.
*
* @param WP_REST_Request $request The REST request object.
*
* @return bool|WP_Error True if the request has permission, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( current_user_can( 'edit_posts' ) ) {
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_cannot_read_block_editor_assets',
__( 'Sorry, you are not allowed to read the block editor assets.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}

/**
* Retrieves the block editor assets schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}

$schema = array(
'type' => 'object',
'properties' => array(
'styles' => array(
'description' => esc_html__( 'Style link tags for the block editor.', 'gutenberg' ),
'type' => 'string',
),
'scripts' => array(
'description' => esc_html__( 'Script tags for the block editor.', 'gutenberg' ),
'type' => 'string',
),
),
);

$this->schema = $schema;

return $this->add_additional_fields_schema( $this->schema );
}
}
}
9 changes: 9 additions & 0 deletions lib/experimental/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ function gutenberg_register_block_editor_settings() {
}
add_action( 'rest_api_init', 'gutenberg_register_block_editor_settings' );

/**
* Registers the block editor assets REST API route.
*/
function gutenberg_register_block_editor_assets() {
$editor_assets = new WP_REST_Block_Editor_Assets_Controller();
$editor_assets->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_block_editor_assets' );


/**
* Shim for get_sample_permalink() to add support for auto-draft status.
Expand Down
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( ! class_exists( 'WP_REST_Block_Editor_Settings_Controller' ) ) {
require_once __DIR__ . '/experimental/class-wp-rest-block-editor-settings-controller.php';
}
require_once __DIR__ . '/experimental/class-wp-rest-block-editor-assets-controller.php';

// WordPress 6.6 compat.
require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* Unit tests covering WP_REST_Block_Editor_Assets_Controller functionality.
*
* @package gutenberg
*/

if ( ! defined( 'REST_REQUEST' ) ) {
define( 'REST_REQUEST', true );
}

class WP_REST_Block_Editor_Assets_Controller_Test extends WP_Test_REST_Controller_Testcase {
/**
* @var int
*/
protected static $admin_id;

/**
* @var int
*/
protected static $subscriber_id;

public function set_up() {
parent::set_up();
}

/**
* Create fake data before test runs.
*
* @param WP_UnitTest_Factory $factory Helper that lets us create fake data.
*/
public static function wpSetupBeforeClass( $factory ) {
self::$admin_id = $factory->user->create(
array(
'role' => 'administrator',
)
);

self::$subscriber_id = $factory->user->create(
array(
'role' => 'subscriber',
)
);
}

/**
* Clean up fake data.
*/
public static function wpTearDownAfterClass() {
self::delete_user( self::$admin_id );
self::delete_user( self::$subscriber_id );
}

public function tear_down() {
parent::tear_down();
}

public function test_register_routes() {
$routes = rest_get_server()->get_routes();

$this->assertArrayHasKey(
'/__experimental/wp-block-editor/v1/editor-assets',
$routes
);
}

public function test_get_items_without_user() {
wp_set_current_user( 0 );

$request = new WP_REST_Request( 'GET', '/__experimental/wp-block-editor/v1/editor-assets' );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_cannot_read_block_editor_assets', $response, 401 );
}

public function test_get_items_without_permissions() {
wp_set_current_user( self::$subscriber_id );

$request = new WP_REST_Request( 'GET', '/__experimental/wp-block-editor/v1/editor-assets' );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_cannot_read_block_editor_assets', $response, 403 );
}

public function test_get_items() {
wp_set_current_user( self::$admin_id );

$request = new WP_REST_Request( 'GET', '/__experimental/wp-block-editor/v1/editor-assets' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$this->assertArrayHasKey( 'styles', $data, 'Editor assets should include styles.' );
$this->assertArrayHasKey( 'scripts', $data, 'Editor assets should include scripts.' );
}

public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/__experimental/wp-block-editor/v1/editor-assets' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];

$this->assertCount( 2, $properties, 'Schema properties array does not have exactly 2 elements' );
$this->assertArrayHasKey( 'styles', $properties, 'Schema properties array does not have "id" key' );
$this->assertArrayHasKey( 'scripts', $properties, 'Schema properties array does not have "styles" key' );
}

/**
* @doesNotPerformAssertions
*/
public function test_create_item() {}

/**
* @doesNotPerformAssertions
*/
public function test_update_item() {}

/**
* @doesNotPerformAssertions
*/
public function test_get_item() {}

/**
* @doesNotPerformAssertions
*/
public function test_delete_item() {}

/**
* @doesNotPerformAssertions
*/
public function test_prepare_item() {}

/**
* @doesNotPerformAssertions
*/
public function test_context_param() {}
}
Loading