PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
gutenberg / lib / experimental / dashboard-widgets / class-wp-rest-widget-modules-controller.php

class-wp-rest-widget-modules-controller.php in Gutenberg 24.1.0, at lib/experimental/dashboard-widgets/class-wp-rest-widget-modules-controller.php

413 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Widget Modules REST API: WP_REST_Widget_Modules_Controller class.
4 *
5 * @package gutenberg
6 */
7
8 if ( ! class_exists( 'WP_REST_Widget_Modules_Controller' ) ) {
9
10 /**
11 * Internal REST controller exposing the widget type registry.
12 *
13 * Reads from `WP_Widget_Type_Registry`. Read-only collection and item
14 * endpoints. Render and encode endpoints are intentionally absent:
15 * consumers import the render module on the client and render in JS,
16 * so there is no server-rendered HTML to expose.
17 *
18 * The endpoint lives at `/wp/v2/widget-modules` because the entity
19 * `(kind: 'root', name: 'widgetType')` and the path
20 * `/wp/v2/widget-types` are already taken by the legacy widgets API.
21 */
22 class WP_REST_Widget_Modules_Controller extends WP_REST_Controller {
23
24 /**
25 * Constructor.
26 */
27 public function __construct() {
28 $this->namespace = 'wp/v2';
29 $this->rest_base = 'widget-modules';
30 }
31
32 /**
33 * Registers the widget module routes.
34 */
35 public function register_routes() {
36 register_rest_route(
37 $this->namespace,
38 '/' . $this->rest_base,
39 array(
40 array(
41 'methods' => WP_REST_Server::READABLE,
42 'callback' => array( $this, 'get_items' ),
43 'permission_callback' => array( $this, 'get_items_permissions_check' ),
44 'args' => $this->get_collection_params(),
45 ),
46 'schema' => array( $this, 'get_public_item_schema' ),
47 )
48 );
49
50 register_rest_route(
51 $this->namespace,
52 '/' . $this->rest_base . '/(?P<id>[a-z0-9-]+\/[a-z0-9-]+)',
53 array(
54 'args' => array(
55 'id' => array(
56 'description' => __( 'Widget module name including namespace.', 'gutenberg' ),
57 'type' => 'string',
58 ),
59 ),
60 array(
61 'methods' => WP_REST_Server::READABLE,
62 'callback' => array( $this, 'get_item' ),
63 'permission_callback' => array( $this, 'get_item_permissions_check' ),
64 'args' => array(),
65 ),
66 'schema' => array( $this, 'get_public_item_schema' ),
67 )
68 );
69 }
70
71 /**
72 * Checks whether a given request has permission to read widget
73 * modules.
74 *
75 * @param WP_REST_Request $request Full details about the request.
76 * @return true|WP_Error True if the request has read access, WP_Error
77 * otherwise.
78 */
79 public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
80 return $this->check_read_permission();
81 }
82
83 /**
84 * Checks whether a given request has permission to read a single
85 * widget module.
86 *
87 * @param WP_REST_Request $request Full details about the request.
88 * @return true|WP_Error True if the request has read access, WP_Error
89 * otherwise.
90 */
91 public function get_item_permissions_check( $request ) {
92 $check = $this->check_read_permission();
93 if ( is_wp_error( $check ) ) {
94 return $check;
95 }
96
97 $widget_type = WP_Widget_Type_Registry::get_instance()->get_registered( $request['id'] );
98 if ( null === $widget_type ) {
99 return new WP_Error(
100 'rest_widget_module_invalid',
101 __( 'Invalid widget module name.', 'gutenberg' ),
102 array( 'status' => 404 )
103 );
104 }
105
106 return true;
107 }
108
109 /**
110 * Verifies the user has the basic read capability.
111 *
112 * Widget modules are not sensitive data; they describe what is
113 * available to render. Gating at the same level as the dashboard
114 * page menu (which requires `read`) keeps access consistent.
115 *
116 * @return true|WP_Error True if the request is allowed, WP_Error
117 * otherwise.
118 */
119 protected function check_read_permission() {
120 if ( ! current_user_can( 'read' ) ) {
121 return new WP_Error(
122 'rest_cannot_view_widget_modules',
123 __( 'Sorry, you are not allowed to view widget modules.', 'gutenberg' ),
124 array( 'status' => rest_authorization_required_code() )
125 );
126 }
127
128 return true;
129 }
130
131 /**
132 * Retrieves the list of all registered widget modules.
133 *
134 * @param WP_REST_Request $request Full details about the request.
135 * @return WP_REST_Response Response object on success.
136 */
137 public function get_items( $request ) {
138 $registered = WP_Widget_Type_Registry::get_instance()->get_all_registered();
139 $data = array();
140
141 foreach ( $registered as $widget_type ) {
142 $item = $this->prepare_item_for_response( $widget_type, $request );
143 $data[] = $this->prepare_response_for_collection( $item );
144 }
145
146 return rest_ensure_response( $data );
147 }
148
149 /**
150 * Retrieves a single widget module from the collection.
151 *
152 * @param WP_REST_Request $request Full details about the request.
153 * @return WP_REST_Response|WP_Error Response object on success, or
154 * WP_Error on failure.
155 */
156 public function get_item( $request ) {
157 $widget_type = WP_Widget_Type_Registry::get_instance()->get_registered( $request['id'] );
158 if ( null === $widget_type ) {
159 return new WP_Error(
160 'rest_widget_module_invalid',
161 __( 'Invalid widget module name.', 'gutenberg' ),
162 array( 'status' => 404 )
163 );
164 }
165
166 return rest_ensure_response( $this->prepare_item_for_response( $widget_type, $request ) );
167 }
168
169 /**
170 * Prepares a widget type object for serialization.
171 *
172 * @param WP_Widget_Type $item Widget type instance.
173 * @param WP_REST_Request $request Full details about the request.
174 * @return WP_REST_Response Response object containing the serialized
175 * widget module data.
176 */
177 public function prepare_item_for_response( $item, $request ) {
178 $widget_type = $item;
179 $fields = $this->get_fields_for_response( $request );
180 $data = array();
181
182 if ( rest_is_field_included( 'name', $fields ) ) {
183 $data['name'] = $widget_type->name;
184 }
185
186 if ( rest_is_field_included( 'render_module', $fields ) ) {
187 $data['render_module'] = $widget_type->render_module;
188 }
189
190 if ( rest_is_field_included( 'widget_module', $fields ) ) {
191 $data['widget_module'] = $widget_type->widget_module;
192 }
193
194 if ( rest_is_field_included( 'presentation', $fields ) ) {
195 $data['presentation'] = $widget_type->presentation;
196 }
197
198 if ( rest_is_field_included( 'category', $fields ) ) {
199 $data['category'] = $widget_type->category;
200 }
201
202 if ( rest_is_field_included( 'title', $fields ) ) {
203 $data['title'] = $widget_type->title;
204 }
205
206 if ( rest_is_field_included( 'description', $fields ) ) {
207 $data['description'] = $widget_type->description;
208 }
209
210 if ( rest_is_field_included( 'help', $fields ) ) {
211 $data['help'] = $widget_type->help;
212 }
213
214 if ( rest_is_field_included( 'icon', $fields ) ) {
215 $data['icon'] = $widget_type->icon;
216 }
217
218 if ( rest_is_field_included( 'actions', $fields ) ) {
219 $data['actions'] = $widget_type->actions;
220 }
221
222 if ( rest_is_field_included( 'attributes', $fields ) ) {
223 $data['attributes'] = $widget_type->attributes;
224 }
225
226 if ( rest_is_field_included( 'keywords', $fields ) ) {
227 $data['keywords'] = $widget_type->keywords;
228 }
229
230 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
231 $data = $this->add_additional_fields_to_object( $data, $request );
232 $data = $this->filter_response_by_context( $data, $context );
233
234 return rest_ensure_response( $data );
235 }
236
237 /**
238 * Retrieves the widget module schema, conforming to JSON Schema.
239 *
240 * @return array Item schema data.
241 */
242 public function get_item_schema() {
243 if ( $this->schema ) {
244 return $this->add_additional_fields_schema( $this->schema );
245 }
246
247 $schema = array(
248 '$schema' => 'http://json-schema.org/draft-04/schema#',
249 'title' => 'widget-module',
250 'type' => 'object',
251 'properties' => array(
252 'name' => array(
253 'description' => __( 'Widget module name including namespace.', 'gutenberg' ),
254 'type' => 'string',
255 'context' => array( 'view', 'edit', 'embed' ),
256 'readonly' => true,
257 ),
258
259 'render_module' => array(
260 'description' => __( 'Script-module handle for the widget render entry point.', 'gutenberg' ),
261 'type' => array( 'string', 'null' ),
262 'context' => array( 'view', 'edit', 'embed' ),
263 'readonly' => true,
264 ),
265
266 'widget_module' => array(
267 'description' => __( 'Script-module handle for the widget metadata entry point.', 'gutenberg' ),
268 'type' => array( 'string', 'null' ),
269 'context' => array( 'view', 'edit', 'embed' ),
270 'readonly' => true,
271 ),
272
273 'presentation' => array(
274 'description' => __( 'Authoring intent about how the widget wants to render.', 'gutenberg' ),
275 'type' => array( 'string', 'null' ),
276 'enum' => array_merge( WP_Widget_Type::PRESENTATION_VALUES, array( null ) ),
277 'context' => array( 'view', 'edit', 'embed' ),
278 'readonly' => true,
279 ),
280
281 'category' => array(
282 'description' => __( 'Widget types are grouped into categories to help users browse and discover them.', 'gutenberg' ),
283 'type' => array( 'string', 'null' ),
284 'context' => array( 'view', 'edit', 'embed' ),
285 'readonly' => true,
286 ),
287
288 'title' => array(
289 'description' => __( 'Human-readable title that names the widget type. Translatable.', 'gutenberg' ),
290 'type' => array( 'string', 'null' ),
291 'context' => array( 'view', 'edit', 'embed' ),
292 'readonly' => true,
293 ),
294
295 'description' => array(
296 'description' => __( 'Human-readable description of what the widget type does. Translatable.', 'gutenberg' ),
297 'type' => array( 'string', 'null' ),
298 'context' => array( 'view', 'edit', 'embed' ),
299 'readonly' => true,
300 ),
301
302 'help' => array(
303 'description' => __( 'Contextual help note for the widget type: content plus optional links. Translatable.', 'gutenberg' ),
304 'type' => array( 'object', 'null' ),
305 'properties' => array(
306 'content' => array(
307 'description' => __( 'Help content; may carry minimal inline emphasis.', 'gutenberg' ),
308 'type' => 'string',
309 ),
310 'links' => array(
311 'description' => __( 'Optional links contextual to the help content.', 'gutenberg' ),
312 'type' => 'array',
313 'items' => array(
314 'type' => 'object',
315 'properties' => array(
316 'label' => array( 'type' => 'string' ),
317 'href' => array( 'type' => 'string' ),
318 ),
319 ),
320 ),
321 ),
322 'context' => array( 'view', 'edit', 'embed' ),
323 'readonly' => true,
324 ),
325
326 'icon' => array(
327 'description' => __( 'Registered icon name identifying the widget type visually.', 'gutenberg' ),
328 'type' => array( 'string', 'null' ),
329 'context' => array( 'view', 'edit', 'embed' ),
330 'readonly' => true,
331 ),
332
333 'actions' => array(
334 'description' => __( 'Declarative actions the widget type exposes. Labels are translatable.', 'gutenberg' ),
335 'type' => array( 'array', 'null' ),
336 'items' => array(
337 'type' => 'object',
338 'properties' => array(
339 'id' => array( 'type' => 'string' ),
340 'label' => array( 'type' => 'string' ),
341 'icon' => array( 'type' => 'string' ),
342 'relevance' => array(
343 'type' => 'string',
344 'enum' => array( 'high', 'medium', 'low' ),
345 ),
346 'href' => array( 'type' => 'string' ),
347 'download' => array( 'type' => array( 'string', 'boolean' ) ),
348 'openInNewTab' => array( 'type' => 'boolean' ),
349 ),
350 ),
351 'context' => array( 'view', 'edit', 'embed' ),
352 'readonly' => true,
353 ),
354
355 'attributes' => array(
356 'description' => __( 'Attribute schema hosts render settings from: the JSON-expressible subset of a DataViews field per entry. User-facing strings are translatable.', 'gutenberg' ),
357 'type' => array( 'array', 'null' ),
358 'items' => array(
359 'type' => 'object',
360 'properties' => array(
361 'id' => array( 'type' => 'string' ),
362 'type' => array( 'type' => 'string' ),
363 'label' => array( 'type' => 'string' ),
364 'header' => array( 'type' => 'string' ),
365 'description' => array( 'type' => 'string' ),
366 'placeholder' => array( 'type' => 'string' ),
367 'readOnly' => array( 'type' => 'boolean' ),
368 'isDisabled' => array( 'type' => 'boolean' ),
369 'enableSorting' => array( 'type' => 'boolean' ),
370 'enableHiding' => array( 'type' => 'boolean' ),
371 'enableGlobalSearch' => array( 'type' => 'boolean' ),
372 'elements' => array(
373 'type' => 'array',
374 'items' => array(
375 'type' => 'object',
376 'properties' => array(
377 'value' => array( 'type' => array( 'string', 'integer', 'number', 'boolean', 'null' ) ),
378 'label' => array( 'type' => 'string' ),
379 'description' => array( 'type' => 'string' ),
380 ),
381 ),
382 ),
383 'filterBy' => array( 'type' => array( 'object', 'boolean' ) ),
384 'format' => array( 'type' => 'object' ),
385 'isValid' => array( 'type' => 'object' ),
386 'Edit' => array( 'type' => array( 'string', 'object' ) ),
387 'relevance' => array(
388 'type' => 'string',
389 'enum' => array( 'high', 'medium', 'low' ),
390 ),
391 ),
392 ),
393 'context' => array( 'view', 'edit', 'embed' ),
394 'readonly' => true,
395 ),
396
397 'keywords' => array(
398 'description' => __( 'Alternative terms used to match the widget type when searching, e.g. "calendar" for an events widget. Translatable.', 'gutenberg' ),
399 'type' => array( 'array', 'null' ),
400 'items' => array( 'type' => 'string' ),
401 'context' => array( 'view', 'edit', 'embed' ),
402 'readonly' => true,
403 ),
404 ),
405 );
406
407 $this->schema = $schema;
408
409 return $this->add_additional_fields_schema( $this->schema );
410 }
411 }
412 }
413