<?php
/**
* Widget Modules REST API: WP_REST_Widget_Modules_Controller class.
*
* @package gutenberg
*/
if ( ! class_exists( 'WP_REST_Widget_Modules_Controller' ) ) {
/**
* Internal REST controller exposing the widget type registry.
*
* Reads from `WP_Widget_Type_Registry`. Read-only collection and item
* endpoints. Render and encode endpoints are intentionally absent:
* consumers import the render module on the client and render in JS,
* so there is no server-rendered HTML to expose.
*
* The endpoint lives at `/wp/v2/widget-modules` because the entity
* `(kind: 'root', name: 'widgetType')` and the path
* `/wp/v2/widget-types` are already taken by the legacy widgets API.
*/
class WP_REST_Widget_Modules_Controller extends WP_REST_Controller {
/**
* Constructor.
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'widget-modules';
}
/**
* Registers the widget module 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' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[a-z0-9-]+\/[a-z0-9-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Widget module name including namespace.', 'gutenberg' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Checks whether a given request has permission to read widget
* modules.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error
* otherwise.
*/
public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return $this->check_read_permission();
}
/**
* Checks whether a given request has permission to read a single
* widget module.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error
* otherwise.
*/
public function get_item_permissions_check( $request ) {
$check = $this->check_read_permission();
if ( is_wp_error( $check ) ) {
return $check;
}
$widget_type = WP_Widget_Type_Registry::get_instance()->get_registered( $request['id'] );
if ( null === $widget_type ) {
return new WP_Error(
'rest_widget_module_invalid',
__( 'Invalid widget module name.', 'gutenberg' ),
array( 'status' => 404 )
);
}
return true;
}
/**
* Verifies the user has the basic read capability.
*
* Widget modules are not sensitive data; they describe what is
* available to render. Gating at the same level as the dashboard
* page menu (which requires `read`) keeps the surface consistent.
*
* @return true|WP_Error True if the request is allowed, WP_Error
* otherwise.
*/
protected function check_read_permission() {
if ( ! current_user_can( 'read' ) ) {
return new WP_Error(
'rest_cannot_view_widget_modules',
__( 'Sorry, you are not allowed to view widget modules.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Retrieves the list of all registered widget modules.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Response object on success.
*/
public function get_items( $request ) {
$registered = WP_Widget_Type_Registry::get_instance()->get_all_registered();
$data = array();
foreach ( $registered as $widget_type ) {
$item = $this->prepare_item_for_response( $widget_type, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
return rest_ensure_response( $data );
}
/**
* Retrieves a single widget module from the collection.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or
* WP_Error on failure.
*/
public function get_item( $request ) {
$widget_type = WP_Widget_Type_Registry::get_instance()->get_registered( $request['id'] );
if ( null === $widget_type ) {
return new WP_Error(
'rest_widget_module_invalid',
__( 'Invalid widget module name.', 'gutenberg' ),
array( 'status' => 404 )
);
}
return rest_ensure_response( $this->prepare_item_for_response( $widget_type, $request ) );
}
/**
* Prepares a widget type object for serialization.
*
* @param WP_Widget_Type $item Widget type instance.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Response object containing the serialized
* widget module data.
*/
public function prepare_item_for_response( $item, $request ) {
$widget_type = $item;
$fields = $this->get_fields_for_response( $request );
$data = array();
if ( rest_is_field_included( 'name', $fields ) ) {
$data['name'] = $widget_type->name;
}
if ( rest_is_field_included( 'render_module', $fields ) ) {
$data['render_module'] = $widget_type->render_module;
}
if ( rest_is_field_included( 'widget_module', $fields ) ) {
$data['widget_module'] = $widget_type->widget_module;
}
if ( rest_is_field_included( 'presentation', $fields ) ) {
$data['presentation'] = $widget_type->presentation;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
return rest_ensure_response( $data );
}
/**
* Retrieves the widget module 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(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'widget-module',
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'Widget module name including namespace.', 'gutenberg' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'render_module' => array(
'description' => __( 'Script-module handle for the widget render entry point.', 'gutenberg' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'widget_module' => array(
'description' => __( 'Script-module handle for the widget metadata entry point.', 'gutenberg' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'presentation' => array(
'description' => __( 'Authoring intent about how the widget wants to render.', 'gutenberg' ),
'type' => array( 'string', 'null' ),
'enum' => array_merge( WP_Widget_Type::PRESENTATION_VALUES, array( null ) ),
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
}
}