Diff: STRATO-apps/wordpress_03/app/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * REST API: WP_REST_Widgets_Controller class
4 + *
5 + * @package WordPress
6 + * @subpackage REST_API
7 + * @since 5.8.0
8 + */
9 +
10 + /**
11 + * Core class to access widgets via the REST API.
12 + *
13 + * @since 5.8.0
14 + *
15 + * @see WP_REST_Controller
16 + */
17 + class WP_REST_Widgets_Controller extends WP_REST_Controller {
18 +
19 + /**
20 + * Tracks whether {@see retrieve_widgets()} has been called in the current request.
21 + *
22 + * @since 5.9.0
23 + * @var bool
24 + */
25 + protected $widgets_retrieved = false;
26 +
27 + /**
28 + * Whether the controller supports batching.
29 + *
30 + * @since 5.9.0
31 + * @var array
32 + */
33 + protected $allow_batch = array( 'v1' => true );
34 +
35 + /**
36 + * Widgets controller constructor.
37 + *
38 + * @since 5.8.0
39 + */
40 + public function __construct() {
41 + $this->namespace = 'wp/v2';
42 + $this->rest_base = 'widgets';
43 + }
44 +
45 + /**
46 + * Registers the widget routes for the controller.
47 + *
48 + * @since 5.8.0
49 + */
50 + public function register_routes() {
51 + register_rest_route(
52 + $this->namespace,
53 + $this->rest_base,
54 + array(
55 + array(
56 + 'methods' => WP_REST_Server::READABLE,
57 + 'callback' => array( $this, 'get_items' ),
58 + 'permission_callback' => array( $this, 'get_items_permissions_check' ),
59 + 'args' => $this->get_collection_params(),
60 + ),
61 + array(
62 + 'methods' => WP_REST_Server::CREATABLE,
63 + 'callback' => array( $this, 'create_item' ),
64 + 'permission_callback' => array( $this, 'create_item_permissions_check' ),
65 + 'args' => $this->get_endpoint_args_for_item_schema(),
66 + ),
67 + 'allow_batch' => $this->allow_batch,
68 + 'schema' => array( $this, 'get_public_item_schema' ),
69 + )
70 + );
71 +
72 + register_rest_route(
73 + $this->namespace,
74 + $this->rest_base . '/(?P<id>[\w\-]+)',
75 + array(
76 + array(
77 + 'methods' => WP_REST_Server::READABLE,
78 + 'callback' => array( $this, 'get_item' ),
79 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
80 + 'args' => array(
81 + 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
82 + ),
83 + ),
84 + array(
85 + 'methods' => WP_REST_Server::EDITABLE,
86 + 'callback' => array( $this, 'update_item' ),
87 + 'permission_callback' => array( $this, 'update_item_permissions_check' ),
88 + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
89 + ),
90 + array(
91 + 'methods' => WP_REST_Server::DELETABLE,
92 + 'callback' => array( $this, 'delete_item' ),
93 + 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
94 + 'args' => array(
95 + 'force' => array(
96 + 'description' => __( 'Whether to force removal of the widget, or move it to the inactive sidebar.' ),
97 + 'type' => 'boolean',
98 + ),
99 + ),
100 + ),
101 + 'allow_batch' => $this->allow_batch,
102 + 'schema' => array( $this, 'get_public_item_schema' ),
103 + )
104 + );
105 + }
106 +
107 + /**
108 + * Checks if a given request has access to get widgets.
109 + *
110 + * @since 5.8.0
111 + *
112 + * @param WP_REST_Request $request Full details about the request.
113 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
114 + */
115 + public function get_items_permissions_check( $request ) {
116 + $this->retrieve_widgets();
117 + if ( isset( $request['sidebar'] ) && $this->check_read_sidebar_permission( $request['sidebar'] ) ) {
118 + return true;
119 + }
120 +
121 + foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
122 + if ( $this->check_read_sidebar_permission( $sidebar_id ) ) {
123 + return true;
124 + }
125 + }
126 +
127 + return $this->permissions_check( $request );
128 + }
129 +
130 + /**
131 + * Retrieves a collection of widgets.
132 + *
133 + * @since 5.8.0
134 + *
135 + * @param WP_REST_Request $request Full details about the request.
136 + * @return WP_REST_Response Response object.
137 + */
138 + public function get_items( $request ) {
139 + if ( $request->is_method( 'HEAD' ) ) {
140 + // Return early as this handler doesn't add any response headers.
141 + return new WP_REST_Response( array() );
142 + }
143 +
144 + $this->retrieve_widgets();
145 +
146 + $prepared = array();
147 + $permissions_check = $this->permissions_check( $request );
148 +
149 + foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
150 + if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) {
151 + continue;
152 + }
153 +
154 + if ( is_wp_error( $permissions_check ) && ! $this->check_read_sidebar_permission( $sidebar_id ) ) {
155 + continue;
156 + }
157 +
158 + foreach ( $widget_ids as $widget_id ) {
159 + $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
160 +
161 + if ( ! is_wp_error( $response ) ) {
162 + $prepared[] = $this->prepare_response_for_collection( $response );
163 + }
164 + }
165 + }
166 +
167 + return new WP_REST_Response( $prepared );
168 + }
169 +
170 + /**
171 + * Checks if a given request has access to get a widget.
172 + *
173 + * @since 5.8.0
174 + *
175 + * @param WP_REST_Request $request Full details about the request.
176 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
177 + */
178 + public function get_item_permissions_check( $request ) {
179 + $this->retrieve_widgets();
180 +
181 + $widget_id = $request['id'];
182 + $sidebar_id = wp_find_widgets_sidebar( $widget_id );
183 +
184 + if ( $sidebar_id && $this->check_read_sidebar_permission( $sidebar_id ) ) {
185 + return true;
186 + }
187 +
188 + return $this->permissions_check( $request );
189 + }
190 +
191 + /**
192 + * Checks if a sidebar can be read publicly.
193 + *
194 + * @since 5.9.0
195 + *
196 + * @param string $sidebar_id The sidebar ID.
197 + * @return bool Whether the sidebar can be read.
198 + */
199 + protected function check_read_sidebar_permission( $sidebar_id ) {
200 + $sidebar = wp_get_sidebar( $sidebar_id );
201 +
202 + return ! empty( $sidebar['show_in_rest'] );
203 + }
204 +
205 + /**
206 + * Gets an individual widget.
207 + *
208 + * @since 5.8.0
209 + *
210 + * @param WP_REST_Request $request Full details about the request.
211 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
212 + */
213 + public function get_item( $request ) {
214 + $this->retrieve_widgets();
215 +
216 + $widget_id = $request['id'];
217 + $sidebar_id = wp_find_widgets_sidebar( $widget_id );
218 +
219 + if ( is_null( $sidebar_id ) ) {
220 + return new WP_Error(
221 + 'rest_widget_not_found',
222 + __( 'No widget was found with that id.' ),
223 + array( 'status' => 404 )
224 + );
225 + }
226 +
227 + return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
228 + }
229 +
230 + /**
231 + * Checks if a given request has access to create widgets.
232 + *
233 + * @since 5.8.0
234 + *
235 + * @param WP_REST_Request $request Full details about the request.
236 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
237 + */
238 + public function create_item_permissions_check( $request ) {
239 + return $this->permissions_check( $request );
240 + }
241 +
242 + /**
243 + * Creates a widget.
244 + *
245 + * @since 5.8.0
246 + *
247 + * @param WP_REST_Request $request Full details about the request.
248 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
249 + */
250 + public function create_item( $request ) {
251 + $sidebar_id = $request['sidebar'];
252 +
253 + $widget_id = $this->save_widget( $request, $sidebar_id );
254 +
255 + if ( is_wp_error( $widget_id ) ) {
256 + return $widget_id;
257 + }
258 +
259 + wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
260 +
261 + $request['context'] = 'edit';
262 +
263 + $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
264 +
265 + if ( is_wp_error( $response ) ) {
266 + return $response;
267 + }
268 +
269 + $response->set_status( 201 );
270 +
271 + return $response;
272 + }
273 +
274 + /**
275 + * Checks if a given request has access to update widgets.
276 + *
277 + * @since 5.8.0
278 + *
279 + * @param WP_REST_Request $request Full details about the request.
280 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
281 + */
282 + public function update_item_permissions_check( $request ) {
283 + return $this->permissions_check( $request );
284 + }
285 +
286 + /**
287 + * Updates an existing widget.
288 + *
289 + * @since 5.8.0
290 + *
291 + * @global WP_Widget_Factory $wp_widget_factory
292 + *
293 + * @param WP_REST_Request $request Full details about the request.
294 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
295 + */
296 + public function update_item( $request ) {
297 + global $wp_widget_factory;
298 +
299 + /*
300 + * retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the
301 + * wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global.
302 + *
303 + * When batch requests are processed, this global is not properly updated by previous
304 + * calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets
305 + * sidebar.
306 + *
307 + * See https://core.trac.wordpress.org/ticket/53657.
308 + */
309 + wp_get_sidebars_widgets();
310 + $this->retrieve_widgets();
311 +
312 + $widget_id = $request['id'];
313 + $sidebar_id = wp_find_widgets_sidebar( $widget_id );
314 +
315 + // Allow sidebar to be unset or missing when widget is not a WP_Widget.
316 + $parsed_id = wp_parse_widget_id( $widget_id );
317 + $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
318 + if ( is_null( $sidebar_id ) && $widget_object ) {
319 + return new WP_Error(
320 + 'rest_widget_not_found',
321 + __( 'No widget was found with that id.' ),
322 + array( 'status' => 404 )
323 + );
324 + }
325 +
326 + if (
327 + $request->has_param( 'instance' ) ||
328 + $request->has_param( 'form_data' )
329 + ) {
330 + $maybe_error = $this->save_widget( $request, $sidebar_id );
331 + if ( is_wp_error( $maybe_error ) ) {
332 + return $maybe_error;
333 + }
334 + }
335 +
336 + if ( $request->has_param( 'sidebar' ) ) {
337 + if ( $sidebar_id !== $request['sidebar'] ) {
338 + $sidebar_id = $request['sidebar'];
339 + wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
340 + }
341 + }
342 +
343 + $request['context'] = 'edit';
344 +
345 + return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
346 + }
347 +
348 + /**
349 + * Checks if a given request has access to delete widgets.
350 + *
351 + * @since 5.8.0
352 + *
353 + * @param WP_REST_Request $request Full details about the request.
354 + * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
355 + */
356 + public function delete_item_permissions_check( $request ) {
357 + return $this->permissions_check( $request );
358 + }
359 +
360 + /**
361 + * Deletes a widget.
362 + *
363 + * @since 5.8.0
364 + *
365 + * @global WP_Widget_Factory $wp_widget_factory
366 + * @global array $wp_registered_widget_updates The registered widget update functions.
367 + *
368 + * @param WP_REST_Request $request Full details about the request.
369 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
370 + */
371 + public function delete_item( $request ) {
372 + global $wp_widget_factory, $wp_registered_widget_updates;
373 +
374 + /*
375 + * retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the
376 + * wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global.
377 + *
378 + * When batch requests are processed, this global is not properly updated by previous
379 + * calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets
380 + * sidebar.
381 + *
382 + * See https://core.trac.wordpress.org/ticket/53657.
383 + */
384 + wp_get_sidebars_widgets();
385 + $this->retrieve_widgets();
386 +
387 + $widget_id = $request['id'];
388 + $sidebar_id = wp_find_widgets_sidebar( $widget_id );
389 +
390 + if ( is_null( $sidebar_id ) ) {
391 + return new WP_Error(
392 + 'rest_widget_not_found',
393 + __( 'No widget was found with that id.' ),
394 + array( 'status' => 404 )
395 + );
396 + }
397 +
398 + $request['context'] = 'edit';
399 +
400 + if ( $request['force'] ) {
401 + $response = $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
402 +
403 + $parsed_id = wp_parse_widget_id( $widget_id );
404 + $id_base = $parsed_id['id_base'];
405 +
406 + $original_post = $_POST;
407 + $original_request = $_REQUEST;
408 +
409 + $_POST = array(
410 + 'sidebar' => $sidebar_id,
411 + "widget-$id_base" => array(),
412 + 'the-widget-id' => $widget_id,
413 + 'delete_widget' => '1',
414 + );
415 + $_REQUEST = $_POST;
416 +
417 + /** This action is documented in wp-admin/widgets-form.php */
418 + do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
419 +
420 + $callback = $wp_registered_widget_updates[ $id_base ]['callback'];
421 + $params = $wp_registered_widget_updates[ $id_base ]['params'];
422 +
423 + if ( is_callable( $callback ) ) {
424 + ob_start();
425 + call_user_func_array( $callback, $params );
426 + ob_end_clean();
427 + }
428 +
429 + $_POST = $original_post;
430 + $_REQUEST = $original_request;
431 +
432 + $widget_object = $wp_widget_factory->get_widget_object( $id_base );
433 +
434 + if ( $widget_object ) {
435 + /*
436 + * WP_Widget sets `updated = true` after an update to prevent more than one widget
437 + * from being saved per request. This isn't what we want in the REST API, though,
438 + * as we support batch requests.
439 + */
440 + $widget_object->updated = false;
441 + }
442 +
443 + wp_assign_widget_to_sidebar( $widget_id, '' );
444 +
445 + $response->set_data(
446 + array(
447 + 'deleted' => true,
448 + 'previous' => $response->get_data(),
449 + )
450 + );
451 + } else {
452 + wp_assign_widget_to_sidebar( $widget_id, 'wp_inactive_widgets' );
453 +
454 + $response = $this->prepare_item_for_response(
455 + array(
456 + 'sidebar_id' => 'wp_inactive_widgets',
457 + 'widget_id' => $widget_id,
458 + ),
459 + $request
460 + );
461 + }
462 +
463 + /**
464 + * Fires after a widget is deleted via the REST API.
465 + *
466 + * @since 5.8.0
467 + *
468 + * @param string $widget_id ID of the widget marked for deletion.
469 + * @param string $sidebar_id ID of the sidebar the widget was deleted from.
470 + * @param WP_REST_Response|WP_Error $response The response data, or WP_Error object on failure.
471 + * @param WP_REST_Request $request The request sent to the API.
472 + */
473 + do_action( 'rest_delete_widget', $widget_id, $sidebar_id, $response, $request );
474 +
475 + return $response;
476 + }
477 +
478 + /**
479 + * Performs a permissions check for managing widgets.
480 + *
481 + * @since 5.8.0
482 + *
483 + * @param WP_REST_Request $request Full details about the request.
484 + * @return true|WP_Error
485 + */
486 + protected function permissions_check( $request ) {
487 + if ( ! current_user_can( 'edit_theme_options' ) ) {
488 + return new WP_Error(
489 + 'rest_cannot_manage_widgets',
490 + __( 'Sorry, you are not allowed to manage widgets on this site.' ),
491 + array(
492 + 'status' => rest_authorization_required_code(),
493 + )
494 + );
495 + }
496 +
497 + return true;
498 + }
499 +
500 + /**
501 + * Looks for "lost" widgets once per request.
502 + *
503 + * @since 5.9.0
504 + *
505 + * @see retrieve_widgets()
506 + */
507 + protected function retrieve_widgets() {
508 + if ( ! $this->widgets_retrieved ) {
509 + retrieve_widgets();
510 + $this->widgets_retrieved = true;
511 + }
512 + }
513 +
514 + /**
515 + * Saves the widget in the request object.
516 + *
517 + * @since 5.8.0
518 + *
519 + * @global WP_Widget_Factory $wp_widget_factory
520 + * @global array $wp_registered_widget_updates The registered widget update functions.
521 + *
522 + * @param WP_REST_Request $request Full details about the request.
523 + * @param string $sidebar_id ID of the sidebar the widget belongs to.
524 + * @return string|WP_Error The saved widget ID.
525 + */
526 + protected function save_widget( $request, $sidebar_id ) {
527 + global $wp_widget_factory, $wp_registered_widget_updates;
528 +
529 + require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
530 +
531 + if ( isset( $request['id'] ) ) {
532 + // Saving an existing widget.
533 + $id = $request['id'];
534 + $parsed_id = wp_parse_widget_id( $id );
535 + $id_base = $parsed_id['id_base'];
536 + $number = isset( $parsed_id['number'] ) ? $parsed_id['number'] : null;
537 + $widget_object = $wp_widget_factory->get_widget_object( $id_base );
538 + $creating = false;
539 + } elseif ( $request['id_base'] ) {
540 + // Saving a new widget.
541 + $id_base = $request['id_base'];
542 + $widget_object = $wp_widget_factory->get_widget_object( $id_base );
543 + $number = $widget_object ? next_widget_id_number( $id_base ) : null;
544 + $id = $widget_object ? $id_base . '-' . $number : $id_base;
545 + $creating = true;
546 + } else {
547 + return new WP_Error(
548 + 'rest_invalid_widget',
549 + __( 'Widget type (id_base) is required.' ),
550 + array( 'status' => 400 )
551 + );
552 + }
553 +
554 + if ( ! isset( $wp_registered_widget_updates[ $id_base ] ) ) {
555 + return new WP_Error(
556 + 'rest_invalid_widget',
557 + __( 'The provided widget type (id_base) cannot be updated.' ),
558 + array( 'status' => 400 )
559 + );
560 + }
561 +
562 + if ( isset( $request['instance'] ) ) {
563 + if ( ! $widget_object ) {
564 + return new WP_Error(
565 + 'rest_invalid_widget',
566 + __( 'Cannot set instance on a widget that does not extend WP_Widget.' ),
567 + array( 'status' => 400 )
568 + );
569 + }
570 +
571 + if ( isset( $request['instance']['raw'] ) ) {
572 + if ( empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
573 + return new WP_Error(
574 + 'rest_invalid_widget',
575 + __( 'Widget type does not support raw instances.' ),
576 + array( 'status' => 400 )
577 + );
578 + }
579 + $instance = $request['instance']['raw'];
580 + } elseif ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
581 + $serialized_instance = base64_decode( $request['instance']['encoded'] );
582 + if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
583 + return new WP_Error(
584 + 'rest_invalid_widget',
585 + __( 'The provided instance is malformed.' ),
586 + array( 'status' => 400 )
587 + );
588 + }
589 + $instance = unserialize( $serialized_instance );
590 + } else {
591 + return new WP_Error(
592 + 'rest_invalid_widget',
593 + __( 'The provided instance is invalid. Must contain raw OR encoded and hash.' ),
594 + array( 'status' => 400 )
595 + );
596 + }
597 +
598 + $form_data = array(
599 + "widget-$id_base" => array(
600 + $number => $instance,
601 + ),
602 + 'sidebar' => $sidebar_id,
603 + );
604 + } elseif ( isset( $request['form_data'] ) ) {
605 + $form_data = $request['form_data'];
606 + } else {
607 + $form_data = array();
608 + }
609 +
610 + $original_post = $_POST;
611 + $original_request = $_REQUEST;
612 +
613 + foreach ( $form_data as $key => $value ) {
614 + $slashed_value = wp_slash( $value );
615 + $_POST[ $key ] = $slashed_value;
616 + $_REQUEST[ $key ] = $slashed_value;
617 + }
618 +
619 + $callback = $wp_registered_widget_updates[ $id_base ]['callback'];
620 + $params = $wp_registered_widget_updates[ $id_base ]['params'];
621 +
622 + if ( is_callable( $callback ) ) {
623 + ob_start();
624 + call_user_func_array( $callback, $params );
625 + ob_end_clean();
626 + }
627 +
628 + $_POST = $original_post;
629 + $_REQUEST = $original_request;
630 +
631 + if ( $widget_object ) {
632 + // Register any multi-widget that the update callback just created.
633 + $widget_object->_set( $number );
634 + $widget_object->_register_one( $number );
635 +
636 + /*
637 + * WP_Widget sets `updated = true` after an update to prevent more than one widget
638 + * from being saved per request. This isn't what we want in the REST API, though,
639 + * as we support batch requests.
640 + */
641 + $widget_object->updated = false;
642 + }
643 +
644 + /**
645 + * Fires after a widget is created or updated via the REST API.
646 + *
647 + * @since 5.8.0
648 + *
649 + * @param string $id ID of the widget being saved.
650 + * @param string $sidebar_id ID of the sidebar containing the widget being saved.
651 + * @param WP_REST_Request $request Request object.
652 + * @param bool $creating True when creating a widget, false when updating.
653 + */
654 + do_action( 'rest_after_save_widget', $id, $sidebar_id, $request, $creating );
655 +
656 + return $id;
657 + }
658 +
659 + /**
660 + * Prepares the widget for the REST response.
661 + *
662 + * @since 5.8.0
663 + *
664 + * @global WP_Widget_Factory $wp_widget_factory
665 + * @global array $wp_registered_widgets The registered widgets.
666 + *
667 + * @param array $item An array containing a widget_id and sidebar_id.
668 + * @param WP_REST_Request $request Request object.
669 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
670 + */
671 + public function prepare_item_for_response( $item, $request ) {
672 + global $wp_widget_factory, $wp_registered_widgets;
673 +
674 + $widget_id = $item['widget_id'];
675 + $sidebar_id = $item['sidebar_id'];
676 +
677 + if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
678 + return new WP_Error(
679 + 'rest_invalid_widget',
680 + __( 'The requested widget is invalid.' ),
681 + array( 'status' => 500 )
682 + );
683 + }
684 +
685 + $widget = $wp_registered_widgets[ $widget_id ];
686 + // Don't prepare the response body for HEAD requests.
687 + if ( $request->is_method( 'HEAD' ) ) {
688 + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php */
689 + return apply_filters( 'rest_prepare_widget', new WP_REST_Response( array() ), $widget, $request );
690 + }
691 +
692 + $parsed_id = wp_parse_widget_id( $widget_id );
693 + $fields = $this->get_fields_for_response( $request );
694 +
695 + $prepared = array(
696 + 'id' => $widget_id,
697 + 'id_base' => $parsed_id['id_base'],
698 + 'sidebar' => $sidebar_id,
699 + 'rendered' => '',
700 + 'rendered_form' => null,
701 + 'instance' => null,
702 + );
703 +
704 + if (
705 + rest_is_field_included( 'rendered', $fields ) &&
706 + 'wp_inactive_widgets' !== $sidebar_id
707 + ) {
708 + $prepared['rendered'] = trim( wp_render_widget( $widget_id, $sidebar_id ) );
709 + }
710 +
711 + if ( rest_is_field_included( 'rendered_form', $fields ) ) {
712 + $rendered_form = wp_render_widget_control( $widget_id );
713 + if ( ! is_null( $rendered_form ) ) {
714 + $prepared['rendered_form'] = trim( $rendered_form );
715 + }
716 + }
717 +
718 + if ( rest_is_field_included( 'instance', $fields ) ) {
719 + $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
720 + if ( $widget_object && isset( $parsed_id['number'] ) ) {
721 + $all_instances = $widget_object->get_settings();
722 + $instance = $all_instances[ $parsed_id['number'] ];
723 + $serialized_instance = serialize( $instance );
724 + $prepared['instance']['encoded'] = base64_encode( $serialized_instance );
725 + $prepared['instance']['hash'] = wp_hash( $serialized_instance );
726 +
727 + if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
728 + // Use new stdClass so that JSON result is {} and not [].
729 + $prepared['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance;
730 + }
731 + }
732 + }
733 +
734 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
735 + $prepared = $this->add_additional_fields_to_object( $prepared, $request );
736 + $prepared = $this->filter_response_by_context( $prepared, $context );
737 +
738 + $response = rest_ensure_response( $prepared );
739 +
740 + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
741 + $response->add_links( $this->prepare_links( $prepared ) );
742 + }
743 +
744 + /**
745 + * Filters the REST API response for a widget.
746 + *
747 + * @since 5.8.0
748 + *
749 + * @param WP_REST_Response|WP_Error $response The response object, or WP_Error object on failure.
750 + * @param array $widget The registered widget data.
751 + * @param WP_REST_Request $request Request used to generate the response.
752 + */
753 + return apply_filters( 'rest_prepare_widget', $response, $widget, $request );
754 + }
755 +
756 + /**
757 + * Prepares links for the widget.
758 + *
759 + * @since 5.8.0
760 + *
761 + * @param array $prepared Widget.
762 + * @return array Links for the given widget.
763 + */
764 + protected function prepare_links( $prepared ) {
765 + $id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id'];
766 +
767 + return array(
768 + 'self' => array(
769 + 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ),
770 + ),
771 + 'collection' => array(
772 + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
773 + ),
774 + 'about' => array(
775 + 'href' => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ),
776 + 'embeddable' => true,
777 + ),
778 + 'https://api.w.org/sidebar' => array(
779 + 'href' => rest_url( sprintf( 'wp/v2/sidebars/%s/', $prepared['sidebar'] ) ),
780 + ),
781 + );
782 + }
783 +
784 + /**
785 + * Gets the list of collection params.
786 + *
787 + * @since 5.8.0
788 + *
789 + * @return array[]
790 + */
791 + public function get_collection_params() {
792 + return array(
793 + 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
794 + 'sidebar' => array(
795 + 'description' => __( 'The sidebar to return widgets for.' ),
796 + 'type' => 'string',
797 + ),
798 + );
799 + }
800 +
801 + /**
802 + * Retrieves the widget's schema, conforming to JSON Schema.
803 + *
804 + * @since 5.8.0
805 + *
806 + * @return array Item schema data.
807 + */
808 + public function get_item_schema() {
809 + if ( $this->schema ) {
810 + return $this->add_additional_fields_schema( $this->schema );
811 + }
812 +
813 + $this->schema = array(
814 + '$schema' => 'http://json-schema.org/draft-04/schema#',
815 + 'title' => 'widget',
816 + 'type' => 'object',
817 + 'properties' => array(
818 + 'id' => array(
819 + 'description' => __( 'Unique identifier for the widget.' ),
820 + 'type' => 'string',
821 + 'context' => array( 'view', 'edit', 'embed' ),
822 + ),
823 + 'id_base' => array(
824 + 'description' => __( 'The type of the widget. Corresponds to ID in widget-types endpoint.' ),
825 + 'type' => 'string',
826 + 'context' => array( 'view', 'edit', 'embed' ),
827 + ),
828 + 'sidebar' => array(
829 + 'description' => __( 'The sidebar the widget belongs to.' ),
830 + 'type' => 'string',
831 + 'default' => 'wp_inactive_widgets',
832 + 'required' => true,
833 + 'context' => array( 'view', 'edit', 'embed' ),
834 + ),
835 + 'rendered' => array(
836 + 'description' => __( 'HTML representation of the widget.' ),
837 + 'type' => 'string',
838 + 'context' => array( 'view', 'edit', 'embed' ),
839 + 'readonly' => true,
840 + ),
841 + 'rendered_form' => array(
842 + 'description' => __( 'HTML representation of the widget admin form.' ),
843 + 'type' => 'string',
844 + 'context' => array( 'edit' ),
845 + 'readonly' => true,
846 + ),
847 + 'instance' => array(
848 + 'description' => __( 'Instance settings of the widget, if supported.' ),
849 + 'type' => 'object',
850 + 'context' => array( 'edit' ),
851 + 'default' => null,
852 + 'properties' => array(
853 + 'encoded' => array(
854 + 'description' => __( 'Base64 encoded representation of the instance settings.' ),
855 + 'type' => 'string',
856 + 'context' => array( 'edit' ),
857 + ),
858 + 'hash' => array(
859 + 'description' => __( 'Cryptographic hash of the instance settings.' ),
860 + 'type' => 'string',
861 + 'context' => array( 'edit' ),
862 + ),
863 + 'raw' => array(
864 + 'description' => __( 'Unencoded instance settings, if supported.' ),
865 + 'type' => 'object',
866 + 'context' => array( 'edit' ),
867 + ),
868 + ),
869 + ),
870 + 'form_data' => array(
871 + 'description' => __( 'URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.' ),
872 + 'type' => 'string',
873 + 'context' => array(),
874 + 'arg_options' => array(
875 + 'sanitize_callback' => static function ( $form_data ) {
876 + $array = array();
877 + wp_parse_str( $form_data, $array );
878 + return $array;
879 + },
880 + ),
881 + ),
882 + ),
883 + );
884 +
885 + return $this->add_additional_fields_schema( $this->schema );
886 + }
887 + }
888 +