balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-rest-widgets-controller.php (24642B)


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