<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[50995] trunk: REST API: Add widget endpoints</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/50995">50995</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/50995","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>noisysocks</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-05-25 08:26:21 +0000 (Tue, 25 May 2021)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>REST API: Add widget endpoints

Adds the sidebars, widgets and widget-types REST API endpoints from the
Gutenberg plugin.

Fixes <a href="https://core.trac.wordpress.org/ticket/41683">#41683</a>.
Props TimothyBlynJacobs, spacedmonkey, zieladam, jorgefilipecosta, youknowriad, kevin940726.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpwidgetfactoryphp">trunk/src/wp-includes/class-wp-widget-factory.php</a></li>
<li><a href="#trunksrcwpincludesdefaultwidgetsphp">trunk/src/wp-includes/default-widgets.php</a></li>
<li><a href="#trunksrcwpincludesrestapiphp">trunk/src/wp-includes/rest-api.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpnavmenuwidgetphp">trunk/src/wp-includes/widgets/class-wp-nav-menu-widget.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetarchivesphp">trunk/src/wp-includes/widgets/class-wp-widget-archives.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetcalendarphp">trunk/src/wp-includes/widgets/class-wp-widget-calendar.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetcategoriesphp">trunk/src/wp-includes/widgets/class-wp-widget-categories.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetcustomhtmlphp">trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetmediaphp">trunk/src/wp-includes/widgets/class-wp-widget-media.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetmetaphp">trunk/src/wp-includes/widgets/class-wp-widget-meta.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetpagesphp">trunk/src/wp-includes/widgets/class-wp-widget-pages.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetrecentcommentsphp">trunk/src/wp-includes/widgets/class-wp-widget-recent-comments.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetrecentpostsphp">trunk/src/wp-includes/widgets/class-wp-widget-recent-posts.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetrssphp">trunk/src/wp-includes/widgets/class-wp-widget-rss.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetsearchphp">trunk/src/wp-includes/widgets/class-wp-widget-search.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgettagcloudphp">trunk/src/wp-includes/widgets/class-wp-widget-tag-cloud.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgettextphp">trunk/src/wp-includes/widgets/class-wp-widget-text.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsphp">trunk/src/wp-includes/widgets.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemasetupphp">trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php</a></li>
<li><a href="#trunktestsqunitfixtureswpapigeneratedjs">trunk/tests/qunit/fixtures/wp-api-generated.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestsidebarscontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestwidgettypescontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestwidgetscontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsclasswpwidgetblockphp">trunk/src/wp-includes/widgets/class-wp-widget-block.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestsidebarscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-sidebars-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestwidgettypescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-widget-types-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestwidgetscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-widgets-controller.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpwidgetfactoryphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-widget-factory.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-widget-factory.php 2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/class-wp-widget-factory.php   2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -102,4 +102,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->widgets[ $key ]->_register();
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Returns the registered WP_Widget object for the given widget type.
+        *
+        * @since 5.8.0
+        *
+        * @param string $id_base Widget type ID.
+        * @return WP_Widget|null
+        */
+       public function get_widget_object( $id_base ) {
+               foreach ( $this->widgets as $widget_object ) {
+                       if ( $widget_object->id_base === $id_base ) {
+                               return $widget_object;
+                       }
+               }
+
+               return null;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultwidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-widgets.php 2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/default-widgets.php   2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -63,3 +63,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /** WP_Widget_Custom_HTML class */
</span><span class="cx" style="display: block; padding: 0 10px"> require_once ABSPATH . WPINC . '/widgets/class-wp-widget-custom-html.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/** WP_Widget_Block class */
+require_once ABSPATH . WPINC . '/widgets/class-wp-widget-block.php';
</ins></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswprestsidebarscontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php                            (rev 0)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,459 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * REST API: WP_REST_Sidebars_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ *
+ * Copyright (C) 2015  Martin Pettersson
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author    Martin Pettersson <martin_pettersson@outlook.com>
+ * @copyright 2015 Martin Pettersson
+ * @license   GPLv2
+ * @link      https://github.com/martin-pettersson/wp-rest-api-sidebars
+ */
+
+/**
+ * Core class used to manage a site's sidebars.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Sidebars_Controller extends WP_REST_Controller {
+
+       /**
+        * Sidebars controller constructor.
+        *
+        * @since 5.8.0
+        */
+       public function __construct() {
+               $this->namespace = 'wp/v2';
+               $this->rest_base = 'sidebars';
+       }
+
+       /**
+        * Registers the controllers routes.
+        *
+        * @since 5.8.0
+        */
+       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'                => array(
+                                               'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+                                       ),
+                               ),
+                               'schema' => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+
+               register_rest_route(
+                       $this->namespace,
+                       '/' . $this->rest_base . '/(?P<id>[\w-]+)',
+                       array(
+                               array(
+                                       'methods'             => WP_REST_Server::READABLE,
+                                       'callback'            => array( $this, 'get_item' ),
+                                       'permission_callback' => array( $this, 'get_item_permissions_check' ),
+                                       'args'                => array(
+                                               'id'      => array(
+                                                       'description' => __( 'The id of a registered sidebar' ),
+                                                       'type'        => 'string',
+                                               ),
+                                               'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+                                       ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::EDITABLE,
+                                       'callback'            => array( $this, 'update_item' ),
+                                       'permission_callback' => array( $this, 'update_item_permissions_check' ),
+                                       'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
+                               ),
+                               'schema' => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+       }
+
+       /**
+        * Checks if a given request has access to get sidebars.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_items_permissions_check( $request ) {
+               return $this->do_permissions_check();
+       }
+
+       /**
+        * Retrieves the list of sidebars (active or inactive).
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function get_items( $request ) {
+               $data = array();
+               foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) {
+                       $sidebar = $this->get_sidebar( $id );
+
+                       if ( ! $sidebar ) {
+                               continue;
+                       }
+
+                       $data[] = $this->prepare_response_for_collection(
+                               $this->prepare_item_for_response( $sidebar, $request )
+                       );
+               }
+
+               return rest_ensure_response( $data );
+       }
+
+       /**
+        * Checks if a given request has access to get a single sidebar.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_item_permissions_check( $request ) {
+               return $this->do_permissions_check();
+       }
+
+       /**
+        * Retrieves one sidebar from the collection.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function get_item( $request ) {
+               $sidebar = $this->get_sidebar( $request['id'] );
+
+               if ( ! $sidebar ) {
+                       return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.' ), array( 'status' => 404 ) );
+               }
+
+               return $this->prepare_item_for_response( $sidebar, $request );
+       }
+
+       /**
+        * Checks if a given request has access to update sidebars.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function update_item_permissions_check( $request ) {
+               return $this->do_permissions_check();
+       }
+
+       /**
+        * Updates a sidebar.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response Response object on success, or WP_Error object on failure.
+        */
+       public function update_item( $request ) {
+               if ( isset( $request['widgets'] ) ) {
+                       $sidebars = wp_get_sidebars_widgets();
+
+                       foreach ( $sidebars as $sidebar_id => $widgets ) {
+                               foreach ( $widgets as $i => $widget_id ) {
+                                       // This automatically removes the passed widget ids from any other sidebars in use.
+                                       if ( $sidebar_id !== $request['id'] && in_array( $widget_id, $request['widgets'], true ) ) {
+                                               unset( $sidebars[ $sidebar_id ][ $i ] );
+                                       }
+
+                                       // This automatically removes omitted widget ids to the inactive sidebar.
+                                       if ( $sidebar_id === $request['id'] && ! in_array( $widget_id, $request['widgets'], true ) ) {
+                                               $sidebars['wp_inactive_widgets'][] = $widget_id;
+                                       }
+                               }
+                       }
+
+                       $sidebars[ $request['id'] ] = $request['widgets'];
+
+                       wp_set_sidebars_widgets( $sidebars );
+               }
+
+               $request['context'] = 'edit';
+
+               $sidebar = $this->get_sidebar( $request['id'] );
+
+               return $this->prepare_item_for_response( $sidebar, $request );
+       }
+
+       /**
+        * Checks if the user has permissions to make the request.
+        *
+        * @since 5.8.0
+        *
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       protected function do_permissions_check() {
+               // Verify if the current user has edit_theme_options capability.
+               // This capability is required to access the widgets screen.
+               if ( ! current_user_can( 'edit_theme_options' ) ) {
+                       return new WP_Error(
+                               'rest_cannot_manage_widgets',
+                               __( 'Sorry, you are not allowed to manage widgets on this site.' ),
+                               array( 'status' => rest_authorization_required_code() )
+                       );
+               }
+
+               return true;
+       }
+
+       /**
+        * Retrieves the registered sidebar with the given id.
+        *
+        * @since 5.8.0
+        *
+        * @global array $wp_registered_sidebars The registered sidebars.
+        *
+        * @param string|int $id ID of the sidebar.
+        * @return array|null The discovered sidebar, or null if it is not registered.
+        */
+       protected function get_sidebar( $id ) {
+               global $wp_registered_sidebars;
+
+               foreach ( (array) $wp_registered_sidebars as $sidebar ) {
+                       if ( $sidebar['id'] === $id ) {
+                               return $sidebar;
+                       }
+               }
+
+               if ( 'wp_inactive_widgets' === $id ) {
+                       return array(
+                               'id'   => 'wp_inactive_widgets',
+                               'name' => __( 'Inactive widgets' ),
+                       );
+               }
+
+               return null;
+       }
+
+       /**
+        * Prepares a single sidebar output for response.
+        *
+        * @since 5.8.0
+        *
+        * @global array $wp_registered_sidebars The registered sidebars.
+        * @global array $wp_registered_widgets  The registered widgets.
+        *
+        * @param array           $raw_sidebar Sidebar instance.
+        * @param WP_REST_Request $request     Full details about the request.
+        *
+        * @return WP_REST_Response Prepared response object.
+        */
+       public function prepare_item_for_response( $raw_sidebar, $request ) {
+               global $wp_registered_sidebars, $wp_registered_widgets;
+
+               $id      = $raw_sidebar['id'];
+               $sidebar = array( 'id' => $id );
+
+               if ( isset( $wp_registered_sidebars[ $id ] ) ) {
+                       $registered_sidebar = $wp_registered_sidebars[ $id ];
+
+                       $sidebar['status']        = 'active';
+                       $sidebar['name']          = isset( $registered_sidebar['name'] ) ? $registered_sidebar['name'] : '';
+                       $sidebar['description']   = isset( $registered_sidebar['description'] ) ? $registered_sidebar['description'] : '';
+                       $sidebar['class']         = isset( $registered_sidebar['class'] ) ? $registered_sidebar['class'] : '';
+                       $sidebar['before_widget'] = isset( $registered_sidebar['before_widget'] ) ? $registered_sidebar['before_widget'] : '';
+                       $sidebar['after_widget']  = isset( $registered_sidebar['after_widget'] ) ? $registered_sidebar['after_widget'] : '';
+                       $sidebar['before_title']  = isset( $registered_sidebar['before_title'] ) ? $registered_sidebar['before_title'] : '';
+                       $sidebar['after_title']   = isset( $registered_sidebar['after_title'] ) ? $registered_sidebar['after_title'] : '';
+               } else {
+                       $sidebar['status']      = 'inactive';
+                       $sidebar['name']        = $raw_sidebar['name'];
+                       $sidebar['description'] = '';
+                       $sidebar['class']       = '';
+               }
+
+               $fields = $this->get_fields_for_response( $request );
+               if ( rest_is_field_included( 'widgets', $fields ) ) {
+                       $sidebars = wp_get_sidebars_widgets();
+                       $widgets  = array_filter(
+                               isset( $sidebars[ $sidebar['id'] ] ) ? $sidebars[ $sidebar['id'] ] : array(),
+                               static function ( $widget_id ) use ( $wp_registered_widgets ) {
+                                       return isset( $wp_registered_widgets[ $widget_id ] );
+                               }
+                       );
+
+                       $sidebar['widgets'] = $widgets;
+               }
+
+               $schema = $this->get_item_schema();
+               $data   = array();
+               foreach ( $schema['properties'] as $property_id => $property ) {
+                       if ( isset( $sidebar[ $property_id ] ) && true === rest_validate_value_from_schema( $sidebar[ $property_id ], $property ) ) {
+                               $data[ $property_id ] = $sidebar[ $property_id ];
+                       } elseif ( isset( $property['default'] ) ) {
+                               $data[ $property_id ] = $property['default'];
+                       }
+               }
+
+               $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+               $data    = $this->add_additional_fields_to_object( $data, $request );
+               $data    = $this->filter_response_by_context( $data, $context );
+
+               $response = rest_ensure_response( $data );
+
+               $response->add_links( $this->prepare_links( $sidebar ) );
+
+               /**
+                * Filters the REST API response for a sidebar.
+                *
+                * @since 5.8.0
+                *
+                * @param WP_REST_Response $response    The response object.
+                * @param array            $raw_sidebar The raw sidebar data.
+                * @param WP_REST_Request  $request     The request object.
+                */
+               return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request );
+       }
+
+       /**
+        * Prepares links for the sidebar.
+        *
+        * @since 5.8.0
+        *
+        * @param array $sidebar Sidebar.
+        *
+        * @return array Links for the given widget.
+        */
+       protected function prepare_links( $sidebar ) {
+               return array(
+                       'collection'               => array(
+                               'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
+                       ),
+                       'self'                     => array(
+                               'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ),
+                       ),
+                       'https://api.w.org/widget' => array(
+                               'href'       => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ),
+                               'embeddable' => true,
+                       ),
+               );
+       }
+
+       /**
+        * Retrieves the block type' 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'      => 'sidebar',
+                       'type'       => 'object',
+                       'properties' => array(
+                               'id'            => array(
+                                       'description' => __( 'ID of sidebar.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'name'          => array(
+                                       'description' => __( 'Unique name identifying the sidebar.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'description'   => array(
+                                       'description' => __( 'Description of sidebar.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'class'         => array(
+                                       'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'before_widget' => array(
+                                       'description' => __( 'HTML content to prepend to each widget\'s HTML output when assigned to this sidebar. Default is an opening list item element.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'after_widget'  => array(
+                                       'description' => __( 'HTML content to append to each widget\'s HTML output when assigned to this sidebar. Default is a closing list item element.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'before_title'  => array(
+                                       'description' => __( 'HTML content to prepend to the sidebar title when displayed. Default is an opening h2 element.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'after_title'   => array(
+                                       'description' => __( 'HTML content to append to the sidebar title when displayed. Default is a closing h2 element.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'status'        => array(
+                                       'description' => __( 'Status of sidebar.' ),
+                                       'type'        => 'string',
+                                       'enum'        => array( 'active', 'inactive' ),
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'widgets'       => array(
+                                       'description' => __( 'Nested widgets.' ),
+                                       'type'        => 'array',
+                                       'items'       => array(
+                                               'type' => array( 'object', 'string' ),
+                                       ),
+                                       'default'     => array(),
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                       ),
+               );
+
+               $this->schema = $schema;
+
+               return $this->add_additional_fields_schema( $this->schema );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesrestapiendpointsclasswprestwidgettypescontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php                                (rev 0)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php  2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,551 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * REST API: WP_REST_Widget_Types_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Core class to access widget types via the REST API.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Widget_Types_Controller extends WP_REST_Controller {
+
+       /**
+        * Constructor.
+        *
+        * @since 5.8.0
+        */
+       public function __construct() {
+               $this->namespace = 'wp/v2';
+               $this->rest_base = 'widget-types';
+       }
+
+       /**
+        * Registers the widget type routes.
+        *
+        * @since 5.8.0
+        *
+        * @see register_rest_route()
+        */
+       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-zA-Z0-9_-]+)',
+                       array(
+                               'args'   => array(
+                                       'id' => array(
+                                               'description' => __( 'The widget type id.' ),
+                                               'type'        => 'string',
+                                       ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::READABLE,
+                                       'callback'            => array( $this, 'get_item' ),
+                                       'permission_callback' => array( $this, 'get_item_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-zA-Z0-9_-]+)/encode',
+                       array(
+                               'args' => array(
+                                       'id'        => array(
+                                               'description' => __( 'The widget type id.' ),
+                                               'type'        => 'string',
+                                               'required'    => true,
+                                       ),
+                                       'instance'  => array(
+                                               'description' => __( 'Current instance settings of the widget.' ),
+                                               'type'        => 'object',
+                                       ),
+                                       'form_data' => array(
+                                               'description'       => __( 'Serialized widget form data to encode into instance settings.' ),
+                                               'type'              => 'string',
+                                               'sanitize_callback' => function( $string ) {
+                                                       $array = array();
+                                                       wp_parse_str( $string, $array );
+                                                       return $array;
+                                               },
+                                       ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::CREATABLE,
+                                       'permission_callback' => array( $this, 'get_item_permissions_check' ),
+                                       'callback'            => array( $this, 'encode_form_data' ),
+                               ),
+                       )
+               );
+       }
+
+       /**
+        * Checks whether a given request has permission to read widget types.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_items_permissions_check( $request ) {
+               return $this->check_read_permission();
+       }
+
+       /**
+        * Retrieves the list of all widget types.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
+        */
+       public function get_items( $request ) {
+               $data = array();
+               foreach ( $this->get_widgets() as $widget ) {
+                       $widget_type = $this->prepare_item_for_response( $widget, $request );
+                       $data[]      = $this->prepare_response_for_collection( $widget_type );
+               }
+
+               return rest_ensure_response( $data );
+       }
+
+       /**
+        * Checks if a given request has access to read a widget type.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
+        */
+       public function get_item_permissions_check( $request ) {
+               $check = $this->check_read_permission();
+               if ( is_wp_error( $check ) ) {
+                       return $check;
+               }
+               $widget_id   = $request['id'];
+               $widget_type = $this->get_widget( $widget_id );
+               if ( is_wp_error( $widget_type ) ) {
+                       return $widget_type;
+               }
+
+               return true;
+       }
+
+       /**
+        * Checks whether the user can read widget types.
+        *
+        * @since 5.8.0
+        *
+        * @return WP_Error|bool True if the widget type is visible, WP_Error otherwise.
+        */
+       protected function check_read_permission() {
+               if ( ! current_user_can( 'edit_theme_options' ) ) {
+                       return new WP_Error(
+                               'rest_cannot_manage_widgets',
+                               __( 'Sorry, you are not allowed to manage widgets on this site.' ),
+                               array(
+                                       'status' => rest_authorization_required_code(),
+                               )
+                       );
+               }
+
+               return true;
+       }
+
+       /**
+        * Gets the details about the requested widget.
+        *
+        * @since 5.8.0
+        *
+        * @param string $id The widget type id.
+        * @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise.
+        */
+       public function get_widget( $id ) {
+               foreach ( $this->get_widgets() as $widget ) {
+                       if ( $id === $widget['id'] ) {
+                               return $widget;
+                       }
+               }
+
+               return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) );
+       }
+
+       /**
+        * Normalize array of widgets.
+        *
+        * @since 5.8.0
+        *
+        * @global array $wp_registered_widgets The list of registered widgets.
+        *
+        * @return array Array of widgets.
+        */
+       protected function get_widgets() {
+               global $wp_widget_factory, $wp_registered_widgets;
+
+               $widgets = array();
+
+               foreach ( $wp_registered_widgets as $widget ) {
+                       $parsed_id     = wp_parse_widget_id( $widget['id'] );
+                       $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
+
+                       $widget['id']       = $parsed_id['id_base'];
+                       $widget['is_multi'] = (bool) $widget_object;
+
+                       unset( $widget['callback'] );
+
+                       $classname = '';
+                       foreach ( (array) $widget['classname'] as $cn ) {
+                               if ( is_string( $cn ) ) {
+                                       $classname .= '_' . $cn;
+                               } elseif ( is_object( $cn ) ) {
+                                       $classname .= '_' . get_class( $cn );
+                               }
+                       }
+                       $widget['classname'] = ltrim( $classname, '_' );
+
+                       $widgets[] = $widget;
+               }
+
+               return $widgets;
+       }
+
+       /**
+        * Retrieves a single widget type from the collection.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
+        */
+       public function get_item( $request ) {
+               $widget_id   = $request['id'];
+               $widget_type = $this->get_widget( $widget_id );
+               if ( is_wp_error( $widget_type ) ) {
+                       return $widget_type;
+               }
+               $data = $this->prepare_item_for_response( $widget_type, $request );
+
+               return rest_ensure_response( $data );
+       }
+
+       /**
+        * Prepares a widget type object for serialization.
+        *
+        * @since 5.8.0
+        *
+        * @param array           $widget_type Widget type data.
+        * @param WP_REST_Request $request    Full details about the request.
+        * @return WP_REST_Response Widget type data.
+        */
+       public function prepare_item_for_response( $widget_type, $request ) {
+               $fields = $this->get_fields_for_response( $request );
+               $data   = array(
+                       'id' => $widget_type['id'],
+               );
+
+               $schema       = $this->get_item_schema();
+               $extra_fields = array(
+                       'name',
+                       'description',
+                       'is_multi',
+                       'classname',
+                       'widget_class',
+                       'option_name',
+                       'customize_selective_refresh',
+               );
+
+               foreach ( $extra_fields as $extra_field ) {
+                       if ( ! rest_is_field_included( $extra_field, $fields ) ) {
+                               continue;
+                       }
+
+                       if ( isset( $widget_type[ $extra_field ] ) ) {
+                               $field = $widget_type[ $extra_field ];
+                       } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) {
+                               $field = $schema['properties'][ $extra_field ]['default'];
+                       } else {
+                               $field = '';
+                       }
+
+                       $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] );
+               }
+
+               $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+               $data    = $this->add_additional_fields_to_object( $data, $request );
+               $data    = $this->filter_response_by_context( $data, $context );
+
+               $response = rest_ensure_response( $data );
+
+               $response->add_links( $this->prepare_links( $widget_type ) );
+
+               /**
+                * Filters the REST API response for a widget type.
+                *
+                * @since 5.8.0
+                *
+                * @param WP_REST_Response $response    The response object.
+                * @param array            $widget_type The array of widget data.
+                * @param WP_REST_Request  $request     The request object.
+                */
+               return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request );
+       }
+
+       /**
+        * Prepares links for the widget type.
+        *
+        * @since 5.8.0
+        *
+        * @param array $widget_type Widget type data.
+        * @return array Links for the given widget type.
+        */
+       protected function prepare_links( $widget_type ) {
+               return array(
+                       'collection' => array(
+                               'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
+                       ),
+                       'self'       => array(
+                               'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ),
+                       ),
+               );
+       }
+
+       /**
+        * Retrieves the widget type's schema, conforming to JSON Schema.
+        *
+        * @since 5.8.0
+        *
+        * @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-type',
+                       'type'       => 'object',
+                       'properties' => array(
+                               'id'          => array(
+                                       'description' => __( 'Unique slug identifying the widget type.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'name'        => array(
+                                       'description' => __( 'Human-readable name identifying the widget type.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'description' => array(
+                                       'description' => __( 'Description of the widget.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                               ),
+                               'is_multi'    => array(
+                                       'description' => __( 'Whether the widget supports multiple instances' ),
+                                       'type'        => 'boolean',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                       'readonly'    => true,
+                               ),
+                               'classname'   => array(
+                                       'description' => __( 'Class name' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                       ),
+               );
+
+               $this->schema = $schema;
+
+               return $this->add_additional_fields_schema( $this->schema );
+       }
+
+       /**
+        * An RPC-style endpoint which can be used by clients to turn user input in
+        * a widget admin form into an encoded instance object.
+        *
+        * Accepts:
+        *
+        * - id:        A widget type ID.
+        * - instance:  A widget's encoded instance object. Optional.
+        * - form_data: Form data from submitting a widget's admin form. Optional.
+        *
+        * Returns:
+        * - instance: The encoded instance object after updating the widget with
+        *             the given form data.
+        * - form:     The widget's admin form after updating the widget with the
+        *             given form data.
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function encode_form_data( $request ) {
+               global $wp_widget_factory;
+
+               $id            = $request['id'];
+               $widget_object = $wp_widget_factory->get_widget_object( $id );
+
+               if ( ! $widget_object ) {
+                       return new WP_Error(
+                               'rest_invalid_widget',
+                               __( 'Cannot preview a widget that does not extend WP_Widget.' ),
+                               array( 'status' => 400 )
+                       );
+               }
+
+               // Set the widget's number so that the id attributes in the HTML that we
+               // return are predictable.
+               if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) {
+                       $widget_object->_set( (int) $request['number'] );
+               } else {
+                       $widget_object->_set( -1 );
+               }
+
+               if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
+                       $serialized_instance = base64_decode( $request['instance']['encoded'] );
+                       if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
+                               return new WP_Error(
+                                       'rest_invalid_widget',
+                                       __( 'The provided instance is malformed.' ),
+                                       array( 'status' => 400 )
+                               );
+                       }
+                       $instance = unserialize( $serialized_instance );
+               } else {
+                       $instance = array();
+               }
+
+               if (
+                       isset( $request['form_data'][ "widget-$id" ] ) &&
+                       is_array( $request['form_data'][ "widget-$id" ] )
+               ) {
+                       $new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0];
+                       $old_instance = $instance;
+
+                       $instance = $widget_object->update( $new_instance, $old_instance );
+
+                       /** This filter is documented in wp-includes/class-wp-widget.php */
+                       $instance = apply_filters(
+                               'widget_update_callback',
+                               $instance,
+                               $new_instance,
+                               $old_instance,
+                               $widget_object
+                       );
+               }
+
+               $serialized_instance = serialize( $instance );
+
+               $response = array(
+                       'form'     => trim(
+                               $this->get_widget_form(
+                                       $widget_object,
+                                       $instance
+                               )
+                       ),
+                       'preview'  => trim(
+                               $this->get_widget_preview(
+                                       $widget_object,
+                                       $instance
+                               )
+                       ),
+                       'instance' => array(
+                               'encoded' => base64_encode( $serialized_instance ),
+                               'hash'    => wp_hash( $serialized_instance ),
+                       ),
+               );
+
+               if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
+                       // Use new stdClass so that JSON result is {} and not [].
+                       $response['instance']['raw'] = empty( $instance ) ? new stdClass : $instance;
+               }
+
+               return rest_ensure_response( $response );
+       }
+
+       /**
+        * Returns the output of WP_Widget::widget() when called with the provided
+        * instance. Used by encode_form_data() to preview a widget.
+
+        * @param WP_Widget $widget_object Widget object to call widget() on.
+        * @param array     $instance Widget instance settings.
+        * @return string
+        */
+       private function get_widget_preview( $widget_object, $instance ) {
+               ob_start();
+               the_widget( get_class( $widget_object ), $instance );
+               return ob_get_clean();
+       }
+
+       /**
+        * Returns the output of WP_Widget::form() when called with the provided
+        * instance. Used by encode_form_data() to preview a widget's form.
+        *
+        * @param WP_Widget $widget_object Widget object to call widget() on.
+        * @param array     $instance Widget instance settings.
+        * @return string
+        */
+       private function get_widget_form( $widget_object, $instance ) {
+               ob_start();
+
+               /** This filter is documented in wp-includes/class-wp-widget.php */
+               $instance = apply_filters(
+                       'widget_form_callback',
+                       $instance,
+                       $widget_object
+               );
+
+               if ( false !== $instance ) {
+                       $return = $widget_object->form( $instance );
+
+                       /** This filter is documented in wp-includes/class-wp-widget.php */
+                       do_action_ref_array(
+                               'in_widget_form',
+                               array( &$widget_object, &$return, $instance )
+                       );
+               }
+
+               return ob_get_clean();
+       }
+
+       /**
+        * Retrieves the query params for collections.
+        *
+        * @since 5.8.0
+        *
+        * @return array Collection parameters.
+        */
+       public function get_collection_params() {
+               return array(
+                       'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+               );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesrestapiendpointsclasswprestwidgetscontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php                             (rev 0)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php       2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,693 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * REST API: WP_REST_Widgets_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Core class to access widgets via the REST API.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Widgets_Controller extends WP_REST_Controller {
+
+       /**
+        * Widgets controller constructor.
+        *
+        * @since 5.8.0
+        */
+       public function __construct() {
+               $this->namespace = 'wp/v2';
+               $this->rest_base = 'widgets';
+       }
+
+       /**
+        * Registers the widget routes for the controller.
+        *
+        * @since 5.8.0
+        */
+       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(),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::CREATABLE,
+                                       'callback'            => array( $this, 'create_item' ),
+                                       'permission_callback' => array( $this, 'create_item_permissions_check' ),
+                                       'args'                => $this->get_endpoint_args_for_item_schema(),
+                               ),
+                               'allow_batch' => array( 'v1' => true ),
+                               'schema'      => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+
+               register_rest_route(
+                       $this->namespace,
+                       $this->rest_base . '/(?P<id>[\w\-]+)',
+                       array(
+                               array(
+                                       'methods'             => WP_REST_Server::READABLE,
+                                       'callback'            => array( $this, 'get_item' ),
+                                       'permission_callback' => array( $this, 'get_item_permissions_check' ),
+                                       'args'                => array(
+                                               'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+                                       ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::EDITABLE,
+                                       'callback'            => array( $this, 'update_item' ),
+                                       'permission_callback' => array( $this, 'update_item_permissions_check' ),
+                                       'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::DELETABLE,
+                                       'callback'            => array( $this, 'delete_item' ),
+                                       'permission_callback' => array( $this, 'delete_item_permissions_check' ),
+                                       'args'                => array(
+                                               'force' => array(
+                                                       'description' => __( 'Whether to force removal of the widget, or move it to the inactive sidebar.' ),
+                                                       'type'        => 'boolean',
+                                               ),
+                                       ),
+                               ),
+                               'allow_batch' => array( 'v1' => true ),
+                               'schema'      => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+       }
+
+       /**
+        * Checks if a given request has access to get widgets.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_items_permissions_check( $request ) {
+               return $this->permissions_check();
+       }
+
+       /**
+        * Retrieves a collection of widgets.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function get_items( $request ) {
+               $prepared = array();
+
+               foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
+                       if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) {
+                               continue;
+                       }
+
+                       foreach ( $widget_ids as $widget_id ) {
+                               $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
+
+                               if ( ! is_wp_error( $response ) ) {
+                                       $prepared[] = $this->prepare_response_for_collection( $response );
+                               }
+                       }
+               }
+
+               return new WP_REST_Response( $prepared );
+       }
+
+       /**
+        * Checks if a given request has access to get a widget.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_item_permissions_check( $request ) {
+               return $this->permissions_check();
+       }
+
+       /**
+        * Gets an individual widget.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function get_item( $request ) {
+               $widget_id  = $request['id'];
+               $sidebar_id = wp_find_widgets_sidebar( $widget_id );
+
+               if ( is_null( $sidebar_id ) ) {
+                       return new WP_Error(
+                               'rest_widget_not_found',
+                               __( 'No widget was found with that id.' ),
+                               array( 'status' => 404 )
+                       );
+               }
+
+               return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
+       }
+
+       /**
+        * Checks if a given request has access to create widgets.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function create_item_permissions_check( $request ) {
+               return $this->permissions_check();
+       }
+
+       /**
+        * Creates a widget.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function create_item( $request ) {
+               $sidebar_id = $request['sidebar'];
+
+               $widget_id = $this->save_widget( $request );
+
+               if ( is_wp_error( $widget_id ) ) {
+                       return $widget_id;
+               }
+
+               wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
+
+               $request['context'] = 'edit';
+
+               $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
+
+               if ( is_wp_error( $response ) ) {
+                       return $response;
+               }
+
+               $response->set_status( 201 );
+
+               return $response;
+       }
+
+       /**
+        * Checks if a given request has access to update widgets.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function update_item_permissions_check( $request ) {
+               return $this->permissions_check();
+       }
+
+       /**
+        * Updates an existing widget.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function update_item( $request ) {
+               global $wp_widget_factory;
+
+               $widget_id  = $request['id'];
+               $sidebar_id = wp_find_widgets_sidebar( $widget_id );
+
+               // Allow sidebar to be unset or missing when widget is not a WP_Widget.
+               $parsed_id     = wp_parse_widget_id( $widget_id );
+               $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
+               if ( is_null( $sidebar_id ) && $widget_object ) {
+                       return new WP_Error(
+                               'rest_widget_not_found',
+                               __( 'No widget was found with that id.' ),
+                               array( 'status' => 404 )
+                       );
+               }
+
+               if (
+                       $request->has_param( 'instance' ) ||
+                       $request->has_param( 'form_data' )
+               ) {
+                       $maybe_error = $this->save_widget( $request );
+                       if ( is_wp_error( $maybe_error ) ) {
+                               return $maybe_error;
+                       }
+               }
+
+               if ( $request->has_param( 'sidebar' ) ) {
+                       if ( $sidebar_id !== $request['sidebar'] ) {
+                               $sidebar_id = $request['sidebar'];
+                               wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
+                       }
+               }
+
+               $request['context'] = 'edit';
+
+               return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
+       }
+
+       /**
+        * Checks if a given request has access to delete widgets.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function delete_item_permissions_check( $request ) {
+               return $this->permissions_check();
+       }
+
+       /**
+        * Deletes a widget.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function delete_item( $request ) {
+               $widget_id  = $request['id'];
+               $sidebar_id = wp_find_widgets_sidebar( $widget_id );
+
+               if ( is_null( $sidebar_id ) ) {
+                       return new WP_Error(
+                               'rest_widget_not_found',
+                               __( 'No widget was found with that id.' ),
+                               array( 'status' => 404 )
+                       );
+               }
+
+               $request['context'] = 'edit';
+
+               if ( $request['force'] ) {
+                       $prepared = $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
+                       wp_assign_widget_to_sidebar( $widget_id, '' );
+                       $prepared->set_data(
+                               array(
+                                       'deleted'  => true,
+                                       'previous' => $prepared->get_data(),
+                               )
+                       );
+               } else {
+                       wp_assign_widget_to_sidebar( $widget_id, 'wp_inactive_widgets' );
+                       $prepared = $this->prepare_item_for_response(
+                               array(
+                                       'sidebar_id' => 'wp_inactive_widgets',
+                                       'widget_id'  => $widget_id,
+                               ),
+                               $request
+                       );
+               }
+
+               return $prepared;
+       }
+
+       /**
+        * Performs a permissions check for managing widgets.
+        *
+        * @since 5.8.0
+        *
+        * @return true|WP_Error
+        */
+       protected function permissions_check() {
+               if ( ! current_user_can( 'edit_theme_options' ) ) {
+                       return new WP_Error(
+                               'rest_cannot_manage_widgets',
+                               __( 'Sorry, you are not allowed to manage widgets on this site.' ),
+                               array(
+                                       'status' => rest_authorization_required_code(),
+                               )
+                       );
+               }
+
+               return true;
+       }
+
+       /**
+        * Saves the widget in the request object.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        *
+        * @return string|WP_Error The saved widget ID.
+        */
+       protected function save_widget( $request ) {
+               global $wp_widget_factory, $wp_registered_widget_updates;
+
+               require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
+
+               if ( isset( $request['id'] ) ) {
+                       // Saving an existing widget.
+                       $id            = $request['id'];
+                       $parsed_id     = wp_parse_widget_id( $id );
+                       $id_base       = $parsed_id['id_base'];
+                       $number        = isset( $parsed_id['number'] ) ? $parsed_id['number'] : null;
+                       $widget_object = $wp_widget_factory->get_widget_object( $id_base );
+               } elseif ( $request['id_base'] ) {
+                       // Saving a new widget.
+                       $id_base       = $request['id_base'];
+                       $widget_object = $wp_widget_factory->get_widget_object( $id_base );
+                       $number        = $widget_object ? next_widget_id_number( $id_base ) : null;
+                       $id            = $widget_object ? $id_base . '-' . $number : $id_base;
+               } else {
+                       return new WP_Error(
+                               'rest_invalid_widget',
+                               __( 'Widget type (id_base) is required.' ),
+                               array( 'status' => 400 )
+                       );
+               }
+
+               if ( ! isset( $wp_registered_widget_updates[ $id_base ] ) ) {
+                       return new WP_Error(
+                               'rest_invalid_widget',
+                               __( 'The provided widget type (id_base) cannot be updated.' ),
+                               array( 'status' => 400 )
+                       );
+               }
+
+               if ( isset( $request['instance'] ) ) {
+                       if ( ! $widget_object ) {
+                               return new WP_Error(
+                                       'rest_invalid_widget',
+                                       __( 'Cannot set instance on a widget that does not extend WP_Widget.' ),
+                                       array( 'status' => 400 )
+                               );
+                       }
+
+                       if ( isset( $request['instance']['raw'] ) ) {
+                               if ( empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
+                                       return new WP_Error(
+                                               'rest_invalid_widget',
+                                               __( 'Widget type does not support raw instances.' ),
+                                               array( 'status' => 400 )
+                                       );
+                               }
+                               $instance = $request['instance']['raw'];
+                       } elseif ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
+                               $serialized_instance = base64_decode( $request['instance']['encoded'] );
+                               if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
+                                       return new WP_Error(
+                                               'rest_invalid_widget',
+                                               __( 'The provided instance is malformed.' ),
+                                               array( 'status' => 400 )
+                                       );
+                               }
+                               $instance = unserialize( $serialized_instance );
+                       } else {
+                               return new WP_Error(
+                                       'rest_invalid_widget',
+                                       __( 'The provided instance is invalid. Must contain raw OR encoded and hash.' ),
+                                       array( 'status' => 400 )
+                               );
+                       }
+
+                       $form_data = array(
+                               "widget-$id_base" => array(
+                                       $number => $instance,
+                               ),
+                       );
+               } elseif ( isset( $request['form_data'] ) ) {
+                       $form_data = $request['form_data'];
+               } else {
+                       $form_data = array();
+               }
+
+               $original_post    = $_POST;
+               $original_request = $_REQUEST;
+
+               foreach ( $form_data as $key => $value ) {
+                       $slashed_value    = wp_slash( $value );
+                       $_POST[ $key ]    = $slashed_value;
+                       $_REQUEST[ $key ] = $slashed_value;
+               }
+
+               $callback = $wp_registered_widget_updates[ $id_base ]['callback'];
+               $params   = $wp_registered_widget_updates[ $id_base ]['params'];
+
+               if ( is_callable( $callback ) ) {
+                       ob_start();
+                       call_user_func_array( $callback, $params );
+                       ob_end_clean();
+               }
+
+               $_POST    = $original_post;
+               $_REQUEST = $original_request;
+
+               if ( $widget_object ) {
+                       // Register any multi-widget that the update callback just created.
+                       $widget_object->_set( $number );
+                       $widget_object->_register_one( $number );
+
+                       // WP_Widget sets updated = true after an update to prevent more
+                       // than one widget from being saved per request. This isn't what we
+                       // want in the REST API, though, as we support batch requests.
+                       $widget_object->updated = false;
+               }
+
+               return $id;
+       }
+
+       /**
+        * Prepares the widget for the REST response.
+        *
+        * @since 5.8.0
+        *
+        * @global array $wp_registered_sidebars        The registered sidebars.
+        * @global array $wp_registered_widgets         The registered widgets.
+        * @global array $wp_registered_widget_controls The registered widget controls.
+        *
+        * @param array           $item    An array containing a widget_id and sidebar_id.
+        * @param WP_REST_Request $request Request object.
+        * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+        */
+       public function prepare_item_for_response( $item, $request ) {
+               global $wp_widget_factory, $wp_registered_widgets;
+
+               $widget_id  = $item['widget_id'];
+               $sidebar_id = $item['sidebar_id'];
+
+               if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
+                       return new WP_Error(
+                               'rest_invalid_widget',
+                               __( 'The requested widget is invalid.' ),
+                               array( 'status' => 500 )
+                       );
+               }
+
+               $widget    = $wp_registered_widgets[ $widget_id ];
+               $parsed_id = wp_parse_widget_id( $widget_id );
+               $fields    = $this->get_fields_for_response( $request );
+
+               $prepared = array(
+                       'id'            => $widget_id,
+                       'id_base'       => $parsed_id['id_base'],
+                       'sidebar'       => $sidebar_id,
+                       'rendered'      => '',
+                       'rendered_form' => null,
+                       'instance'      => null,
+               );
+
+               if (
+                       rest_is_field_included( 'rendered', $fields ) &&
+                       'wp_inactive_widgets' !== $sidebar_id
+               ) {
+                       $prepared['rendered'] = trim( wp_render_widget( $widget_id, $sidebar_id ) );
+               }
+
+               if ( rest_is_field_included( 'rendered_form', $fields ) ) {
+                       $rendered_form = wp_render_widget_control( $widget_id );
+                       if ( ! is_null( $rendered_form ) ) {
+                               $prepared['rendered_form'] = trim( $rendered_form );
+                       }
+               }
+
+               if ( rest_is_field_included( 'instance', $fields ) ) {
+                       $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
+                       if ( $widget_object && isset( $parsed_id['number'] ) ) {
+                               $all_instances                   = $widget_object->get_settings();
+                               $instance                        = $all_instances[ $parsed_id['number'] ];
+                               $serialized_instance             = serialize( $instance );
+                               $prepared['instance']['encoded'] = base64_encode( $serialized_instance );
+                               $prepared['instance']['hash']    = wp_hash( $serialized_instance );
+
+                               if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
+                                       // Use new stdClass so that JSON result is {} and not [].
+                                       $prepared['instance']['raw'] = empty( $instance ) ? new stdClass : $instance;
+                               }
+                       }
+               }
+
+               $context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
+               $prepared = $this->add_additional_fields_to_object( $prepared, $request );
+               $prepared = $this->filter_response_by_context( $prepared, $context );
+
+               $response = rest_ensure_response( $prepared );
+
+               $response->add_links( $this->prepare_links( $prepared ) );
+
+               /**
+                * Filters the REST API response for a widget.
+                *
+                * @since 5.8.0
+                *
+                * @param WP_REST_Response $response The response object.
+                * @param array            $widget   The registered widget data.
+                * @param WP_REST_Request  $request  Request used to generate the response.
+                */
+               return apply_filters( 'rest_prepare_widget', $response, $widget, $request );
+       }
+
+       /**
+        * Prepares links for the widget.
+        *
+        * @since 5.8.0
+        *
+        * @param array $prepared Widget.
+        * @return array Links for the given widget.
+        */
+       protected function prepare_links( $prepared ) {
+               $id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id'];
+
+               return array(
+                       'self'                      => array(
+                               'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ),
+                       ),
+                       'collection'                => array(
+                               'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
+                       ),
+                       'about'                     => array(
+                               'href'       => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ),
+                               'embeddable' => true,
+                       ),
+                       'https://api.w.org/sidebar' => array(
+                               'href' => rest_url( sprintf( 'wp/v2/sidebars/%s/', $prepared['sidebar'] ) ),
+                       ),
+               );
+       }
+
+       /**
+        * Gets the list of collection params.
+        *
+        * @since 5.8.0
+        *
+        * @return array[]
+        */
+       public function get_collection_params() {
+               return array(
+                       'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+                       'sidebar' => array(
+                               'description' => __( 'The sidebar to return widgets for.' ),
+                               'type'        => 'string',
+                       ),
+               );
+       }
+
+       /**
+        * Retrieves the widget's schema, conforming to JSON Schema.
+        *
+        * @since 5.8.0
+        *
+        * @return array Item schema data.
+        */
+       public function get_item_schema() {
+               if ( $this->schema ) {
+                       return $this->add_additional_fields_schema( $this->schema );
+               }
+
+               $this->schema = array(
+                       '$schema'    => 'http://json-schema.org/draft-04/schema#',
+                       'title'      => 'widget',
+                       'type'       => 'object',
+                       'properties' => array(
+                               'id'            => array(
+                                       'description' => __( 'Unique identifier for the widget.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                               ),
+                               'id_base'       => array(
+                                       'description' => __( 'The type of the widget. Corresponds to ID in widget-types endpoint.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                               ),
+                               'sidebar'       => array(
+                                       'description' => __( 'The sidebar the widget belongs to.' ),
+                                       'type'        => 'string',
+                                       'default'     => 'wp_inactive_widgets',
+                                       'required'    => true,
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                               ),
+                               'rendered'      => array(
+                                       'description' => __( 'HTML representation of the widget.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                       'readonly'    => true,
+                               ),
+                               'rendered_form' => array(
+                                       'description' => __( 'HTML representation of the widget admin form.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'instance'      => array(
+                                       'description' => __( 'Instance settings of the widget, if supported.' ),
+                                       'type'        => 'object',
+                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                       'default'     => null,
+                                       'properties'  => array(
+                                               'encoded' => array(
+                                                       'description' => __( 'Base64 encoded representation of the instance settings.' ),
+                                                       'type'        => 'string',
+                                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                               ),
+                                               'hash'    => array(
+                                                       'description' => __( 'Cryptographic hash of the instance settings.' ),
+                                                       'type'        => 'string',
+                                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                               ),
+                                               'raw'     => array(
+                                                       'description' => __( 'Unencoded instance settings, if supported.' ),
+                                                       'type'        => 'object',
+                                                       'context'     => array( 'view', 'edit', 'embed' ),
+                                               ),
+                                       ),
+                               ),
+                               'form_data'     => array(
+                                       'description' => __( 'URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.' ),
+                                       'type'        => 'string',
+                                       'context'     => array(),
+                                       'arg_options' => array(
+                                               'sanitize_callback' => function( $string ) {
+                                                       $array = array();
+                                                       wp_parse_str( $string, $array );
+                                                       return $array;
+                                               },
+                                       ),
+                               ),
+                       ),
+               );
+
+               return $this->add_additional_fields_schema( $this->schema );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api.php        2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/rest-api.php  2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -313,6 +313,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">        $controller = new WP_REST_Plugins_Controller();
</span><span class="cx" style="display: block; padding: 0 10px">        $controller->register_routes();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Sidebars.
+       $controller = new WP_REST_Sidebars_Controller();
+       $controller->register_routes();
+
+       // Widget Types.
+       $controller = new WP_REST_Widget_Types_Controller();
+       $controller->register_routes();
+
+       // Widgets.
+       $controller = new WP_REST_Widgets_Controller();
+       $controller->register_routes();
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // Block Directory.
</span><span class="cx" style="display: block; padding: 0 10px">        $controller = new WP_REST_Block_Directory_Controller();
</span><span class="cx" style="display: block; padding: 0 10px">        $controller->register_routes();
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpnavmenuwidgetphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-nav-menu-widget.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-nav-menu-widget.php        2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-nav-menu-widget.php  2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,6 +25,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $widget_ops = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Add a navigation menu to your sidebar.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'nav_menu', __( 'Navigation Menu' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetarchivesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-archives.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-archives.php        2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-archives.php  2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_archive',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A monthly archive of your site&#8217;s Posts.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'archives', __( 'Archives' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetblockphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/widgets/class-wp-widget-block.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-block.php                           (rev 0)
+++ trunk/src/wp-includes/widgets/class-wp-widget-block.php     2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,224 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Widget API: WP_Widget_Block class
+ *
+ * @package WordPress
+ * @subpackage Widgets
+ * @since 5.8.0
+ */
+
+/**
+ * Core class used to implement a Block widget.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_Widget
+ */
+class WP_Widget_Block extends WP_Widget {
+
+       /**
+        * Default instance.
+        *
+        * @since 5.8.0
+        * @var array
+        */
+       protected $default_instance = array(
+               'content' => '',
+       );
+
+       /**
+        * Sets up a new Block widget instance.
+        *
+        * @since 5.8.0
+        */
+       public function __construct() {
+               $widget_ops  = array(
+                       'classname'                   => 'widget_block',
+                       'description'                 => __( 'A widget containing a block.' ),
+                       'customize_selective_refresh' => true,
+                       'show_instance_in_rest'       => true,
+               );
+               $control_ops = array(
+                       'width'  => 400,
+                       'height' => 350,
+               );
+               parent::__construct( 'block', __( 'Block' ), $widget_ops, $control_ops );
+               add_filter( 'is_wide_widget_in_customizer', array( $this, 'set_is_wide_widget_in_customizer' ), 10, 2 );
+       }
+
+       /**
+        * Outputs the content for the current Block widget instance.
+        *
+        * @since 5.8.0
+        *
+        * @param array $args Display arguments including 'before_title', 'after_title',
+        *                        'before_widget', and 'after_widget'.
+        * @param array $instance Settings for the current Block widget instance.
+        *
+        * @global WP_Post $post Global post object.
+        */
+       public function widget( $args, $instance ) {
+               $instance = wp_parse_args( $instance, $this->default_instance );
+
+               echo str_replace(
+                       'widget_block',
+                       $this->get_dynamic_classname( $instance['content'] ),
+                       $args['before_widget']
+               );
+
+               // Handle embeds for block widgets.
+               //
+               // When this feature is added to core it may need to be implemented
+               // differently. WP_Widget_Text is a good reference, that applies a
+               // filter for its content, which WP_Embed uses in its constructor.
+               // See https://core.trac.wordpress.org/ticket/51566.
+               global $wp_embed;
+               $content = $wp_embed->run_shortcode( $instance['content'] );
+               $content = $wp_embed->autoembed( $content );
+
+               $content = do_blocks( $content );
+               $content = do_shortcode( $content );
+
+               echo $content;
+
+               echo $args['after_widget'];
+       }
+
+       /**
+        * Calculates the classname to use in the block widget's container HTML.
+        *
+        * Usually this is set to $this->widget_options['classname'] by
+        * dynamic_sidebar(). In this case, however, we want to set the classname
+        * dynamically depending on the block contained by this block widget.
+        *
+        * If a block widget contains a block that has an equivalent legacy widget,
+        * we display that legacy widget's class name. This helps with theme
+        * backwards compatibility.
+        *
+        * @since 5.8.0
+        *
+        * @param array $content The HTML content of the current block widget.
+        *
+        * @return string The classname to use in the block widget's container HTML.
+        */
+       private function get_dynamic_classname( $content ) {
+               $blocks = parse_blocks( $content );
+
+               $block_name = isset( $blocks[0] ) ? $blocks[0]['blockName'] : null;
+
+               switch ( $block_name ) {
+                       case 'core/paragraph':
+                               $classname = 'widget_block widget_text';
+                               break;
+                       case 'core/calendar':
+                               $classname = 'widget_block widget_calendar';
+                               break;
+                       case 'core/search':
+                               $classname = 'widget_block widget_search';
+                               break;
+                       case 'core/html':
+                               $classname = 'widget_block widget_custom_html';
+                               break;
+                       case 'core/archives':
+                               $classname = 'widget_block widget_archive';
+                               break;
+                       case 'core/latest-posts':
+                               $classname = 'widget_block widget_recent_entries';
+                               break;
+                       case 'core/latest-comments':
+                               $classname = 'widget_block widget_recent_comments';
+                               break;
+                       case 'core/tag-cloud':
+                               $classname = 'widget_block widget_tag_cloud';
+                               break;
+                       case 'core/categories':
+                               $classname = 'widget_block widget_categories';
+                               break;
+                       case 'core/audio':
+                               $classname = 'widget_block widget_media_audio';
+                               break;
+                       case 'core/video':
+                               $classname = 'widget_block widget_media_video';
+                               break;
+                       case 'core/image':
+                               $classname = 'widget_block widget_media_image';
+                               break;
+                       case 'core/gallery':
+                               $classname = 'widget_block widget_media_gallery';
+                               break;
+                       case 'core/rss':
+                               $classname = 'widget_block widget_rss';
+                               break;
+                       default:
+                               $classname = 'widget_block';
+               }
+
+               /**
+                * The classname used in the block widget's container HTML.
+                *
+                * This can be set according to the name of the block contained by the
+                * block widget.
+                *
+                * @since 5.8.0
+                *
+                * @param string $classname The classname to be used in the block widget's container HTML, e.g. 'widget_block widget_text'.
+                * @param string $block_name The name of the block contained by the block widget, e.g. 'core/paragraph'.
+                */
+               return apply_filters( 'widget_block_dynamic_classname', $classname, $block_name );
+       }
+
+       /**
+        * Handles updating settings for the current Block widget instance.
+        *
+        * @since 5.8.0
+
+        * @param array $new_instance New settings for this instance as input by the user via
+        *                            WP_Widget::form().
+        * @param array $old_instance Old settings for this instance.
+        *
+        * @return array Settings to save or bool false to cancel saving.
+        */
+       public function update( $new_instance, $old_instance ) {
+               $instance            = array_merge( $this->default_instance, $old_instance );
+               $instance['content'] = $new_instance['content'];
+
+               return $instance;
+       }
+
+       /**
+        * Outputs the Block widget settings form.
+        *
+        * @since 5.8.0
+        *
+        * @param array $instance Current instance.
+        *
+        * @see WP_Widget_Custom_HTML::render_control_template_scripts()
+        */
+       public function form( $instance ) {
+               $instance = wp_parse_args( (array) $instance, $this->default_instance );
+               ?>
+               <p>
+                       <label for="<?php echo $this->get_field_id( 'content' ); ?>"><?php echo __( 'Block HTML:' ); ?></label>
+                       <textarea id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>" rows="6" cols="50" class="widefat code"><?php echo esc_textarea( $instance['content'] ); ?></textarea>
+               </p>
+               <?php
+       }
+
+       /**
+        * Make sure no block widget is considered to be wide.
+        *
+        * @since 5.8.0
+        *
+        * @param boolean $is_wide Is regarded wide.
+        * @param string  $widget_id Widget ID.
+        *
+        * @return bool Updated is_wide value.
+        */
+       public function set_is_wide_widget_in_customizer( $is_wide, $widget_id ) {
+               if ( strpos( $widget_id, 'block-' ) === 0 ) {
+                       return false;
+               }
+
+               return $is_wide;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/widgets/class-wp-widget-block.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludeswidgetsclasswpwidgetcalendarphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-calendar.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-calendar.php        2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-calendar.php  2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -33,6 +33,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_calendar',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A calendar of your site’s posts.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'calendar', __( 'Calendar' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetcategoriesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-categories.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-categories.php      2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-categories.php        2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_categories',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A list or dropdown of categories.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'categories', __( 'Categories' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetcustomhtmlphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php     2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php       2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -45,6 +45,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_custom_html',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Arbitrary HTML code.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                $control_ops = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'width'  => 400,
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-media.php   2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-media.php     2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -59,6 +59,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'description'                 => __( 'A media item.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'mime_type'                   => '',
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetmetaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-meta.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-meta.php    2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-meta.php      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -28,6 +28,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_meta',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Login, RSS, &amp; WordPress.org links.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'meta', __( 'Meta' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetpagesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-pages.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-pages.php   2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-pages.php     2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_pages',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A list of your site&#8217;s Pages.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'pages', __( 'Pages' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetrecentcommentsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-recent-comments.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-recent-comments.php 2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-recent-comments.php   2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_recent_comments',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Your site&#8217;s most recent comments.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'recent-comments', __( 'Recent Comments' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->alt_option_name = 'widget_recent_comments';
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetrecentpostsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-recent-posts.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-recent-posts.php    2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-recent-posts.php      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_recent_entries',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Your site&#8217;s most recent Posts.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'recent-posts', __( 'Recent Posts' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->alt_option_name = 'widget_recent_entries';
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetrssphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-rss.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-rss.php     2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-rss.php       2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,6 +25,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $widget_ops  = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Entries from any RSS or Atom feed.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                $control_ops = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'width'  => 400,
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgetsearchphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-search.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-search.php  2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-search.php    2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,6 +26,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_search',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A search form for your site.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgettagcloudphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-tag-cloud.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-tag-cloud.php       2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-tag-cloud.php 2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,6 +25,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $widget_ops = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'A cloud of your most used tags.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsclasswpwidgettextphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets/class-wp-widget-text.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets/class-wp-widget-text.php    2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets/class-wp-widget-text.php      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -34,6 +34,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'classname'                   => 'widget_text',
</span><span class="cx" style="display: block; padding: 0 10px">                        'description'                 => __( 'Arbitrary text.' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize_selective_refresh' => true,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'show_instance_in_rest'       => true,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                $control_ops = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'width'  => 400,
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets.php 2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-includes/widgets.php   2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -357,6 +357,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.2.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.3.0 Formalized the existing and already documented `...$params` parameter
</span><span class="cx" style="display: block; padding: 0 10px">  *              by adding it to the function signature.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.8.0 Added show_instance_in_rest option.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @global array $wp_registered_widgets            Uses stored registered widgets.
</span><span class="cx" style="display: block; padding: 0 10px">  * @global array $wp_registered_widget_controls    Stores the registered widget controls (options).
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -369,10 +370,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array      $options {
</span><span class="cx" style="display: block; padding: 0 10px">  *     Optional. An array of supplementary widget options for the instance.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- *     @type string $classname   Class name for the widget's HTML container. Default is a shortened
- *                               version of the output callback name.
- *     @type string $description Widget description for display in the widget administration
- *                               panel and/or theme.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *     @type string $classname             Class name for the widget's HTML container. Default is a shortened
+ *                                         version of the output callback name.
+ *     @type string $description           Widget description for display in the widget administration
+ *                                         panel and/or theme.
+ *     @type bool   $show_instance_in_rest Whether to show the widget's instance settings in the REST API.
+ *                                         Only available for WP_Widget based widgets.
</ins><span class="cx" style="display: block; padding: 0 10px">  * }
</span><span class="cx" style="display: block; padding: 0 10px">  * @param mixed      ...$params       Optional additional parameters to pass to the callback function when it's called.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1796,6 +1799,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        register_widget( 'WP_Widget_Custom_HTML' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        register_widget( 'WP_Widget_Block' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Fires after all default WordPress widgets have been registered.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1803,3 +1808,166 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        do_action( 'widgets_init' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Converts a widget ID into its id_base and number components.
+ *
+ * @since 5.8.0
+ *
+ * @param string $id Widget ID.
+ * @return array Array containing a widget's id_base and number components.
+ */
+function wp_parse_widget_id( $id ) {
+       $parsed = array();
+
+       if ( preg_match( '/^(.+)-(\d+)$/', $id, $matches ) ) {
+               $parsed['id_base'] = $matches[1];
+               $parsed['number']  = (int) $matches[2];
+       } else {
+               // Likely an old single widget.
+               $parsed['id_base'] = $id;
+       }
+
+       return $parsed;
+}
+
+/**
+ * Finds the sidebar that a given widget belongs to.
+ *
+ * @since 5.8.0
+ *
+ * @param string $widget_id The widget id to look for.
+ * @return string|null The found sidebar's id, or null if it was not found.
+ */
+function wp_find_widgets_sidebar( $widget_id ) {
+       foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
+               foreach ( $widget_ids as $maybe_widget_id ) {
+                       if ( $maybe_widget_id === $widget_id ) {
+                               return (string) $sidebar_id;
+                       }
+               }
+       }
+
+       return null;
+}
+
+/**
+ * Assigns a widget to the given sidebar.
+ *
+ * @since 5.8.0
+ *
+ * @param string $widget_id  The widget id to assign.
+ * @param string $sidebar_id The sidebar id to assign to. If empty, the widget won't be added to any sidebar.
+ */
+function wp_assign_widget_to_sidebar( $widget_id, $sidebar_id ) {
+       $sidebars = wp_get_sidebars_widgets();
+
+       foreach ( $sidebars as $maybe_sidebar_id => $widgets ) {
+               foreach ( $widgets as $i => $maybe_widget_id ) {
+                       if ( $widget_id === $maybe_widget_id && $sidebar_id !== $maybe_sidebar_id ) {
+                               unset( $sidebars[ $maybe_sidebar_id ][ $i ] );
+                               // We could technically break 2 here, but continue looping in case the id is duplicated.
+                               continue 2;
+                       }
+               }
+       }
+
+       if ( $sidebar_id ) {
+               $sidebars[ $sidebar_id ][] = $widget_id;
+       }
+
+       wp_set_sidebars_widgets( $sidebars );
+}
+
+/**
+ * Calls the render callback of a widget and returns the output.
+ *
+ * @since 5.8.0
+ *
+ * @param string $widget_id Widget ID.
+ * @param string $sidebar_id Sidebar ID.
+ * @return string
+ */
+function wp_render_widget( $widget_id, $sidebar_id ) {
+       global $wp_registered_widgets, $wp_registered_sidebars;
+
+       if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
+               return '';
+       }
+
+       if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
+               $sidebar = $wp_registered_sidebars[ $sidebar_id ];
+       } elseif ( 'wp_inactive_widgets' === $sidebar_id ) {
+               $sidebar = array();
+       } else {
+               return '';
+       }
+
+       $params = array_merge(
+               array(
+                       array_merge(
+                               $sidebar,
+                               array(
+                                       'widget_id'   => $widget_id,
+                                       'widget_name' => $wp_registered_widgets[ $widget_id ]['name'],
+                               )
+                       ),
+               ),
+               (array) $wp_registered_widgets[ $widget_id ]['params']
+       );
+
+       // Substitute HTML `id` and `class` attributes into `before_widget`.
+       $classname_ = '';
+       foreach ( (array) $wp_registered_widgets[ $widget_id ]['classname'] as $cn ) {
+               if ( is_string( $cn ) ) {
+                       $classname_ .= '_' . $cn;
+               } elseif ( is_object( $cn ) ) {
+                       $classname_ .= '_' . get_class( $cn );
+               }
+       }
+       $classname_                 = ltrim( $classname_, '_' );
+       $params[0]['before_widget'] = sprintf( $params[0]['before_widget'], $widget_id, $classname_ );
+
+       /** This filter is documented in wp-includes/widgets.php */
+       $params = apply_filters( 'dynamic_sidebar_params', $params );
+
+       $callback = $wp_registered_widgets[ $widget_id ]['callback'];
+
+       ob_start();
+
+       /** This filter is documented in wp-includes/widgets.php */
+       do_action( 'dynamic_sidebar', $wp_registered_widgets[ $widget_id ] );
+
+       if ( is_callable( $callback ) ) {
+               call_user_func_array( $callback, $params );
+       }
+
+       return ob_get_clean();
+}
+
+/**
+ * Calls the control callback of a widget and returns the output.
+ *
+ * @since 5.8.0
+ *
+ * @param string $id Widget ID.
+ * @return string|null
+ */
+function wp_render_widget_control( $id ) {
+       global $wp_registered_widget_controls;
+
+       if ( ! isset( $wp_registered_widget_controls[ $id ]['callback'] ) ) {
+               return null;
+       }
+
+       $callback = $wp_registered_widget_controls[ $id ]['callback'];
+       $params   = $wp_registered_widget_controls[ $id ]['params'];
+
+       ob_start();
+
+       if ( is_callable( $callback ) ) {
+               call_user_func_array( $callback, $params );
+       }
+
+       return ob_get_clean();
+}
</ins></span></pre></div>
<a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/src/wp-settings.php   2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -265,6 +265,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-directory-controller.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-application-passwords-controller.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-site-health-controller.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-sidebars-controller.php';
+require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php';
+require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestschemasetupphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php  2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php    2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,6 +134,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/plugins',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/block-directory/search',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        '/wp/v2/sidebars',
+                       '/wp/v2/sidebars/(?P<id>[\w-]+)',
+                       '/wp/v2/widget-types',
+                       '/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)',
+                       '/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)/encode',
+                       '/wp/v2/widgets',
+                       '/wp/v2/widgets/(?P<id>[\w\-]+)',
</ins><span class="cx" style="display: block; padding: 0 10px">                         '/wp-site-health/v1',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp-site-health/v1/tests/background-updates',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp-site-health/v1/tests/loopback-requests',
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestsidebarscontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/rest-sidebars-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-sidebars-controller.php                           (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/rest-sidebars-controller.php     2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,627 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Unit tests covering WP_REST_Sidebars_Controller functionality.
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Tests for REST API for Menus.
+ *
+ * @see WP_Test_REST_Controller_Testcase
+ * @group restapi
+ * @covers WP_REST_Sidebars_Controller
+ */
+class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase {
+
+       /**
+        * @var int
+        */
+       protected static $admin_id;
+
+       /**
+        * @var int
+        */
+       protected static $author_id;
+
+       /**
+        * Create fake data before our tests run.
+        *
+        * @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::$author_id = $factory->user->create(
+                       array(
+                               'role' => 'author',
+                       )
+               );
+       }
+
+       public static function wpTearDownAfterClass() {
+               wp_delete_user( self::$admin_id );
+               wp_delete_user( self::$author_id );
+       }
+
+       public function setUp() {
+               parent::setUp();
+
+               wp_set_current_user( self::$admin_id );
+
+               // Unregister all widgets and sidebars.
+               global $wp_registered_sidebars, $_wp_sidebars_widgets;
+               $wp_registered_sidebars = array();
+               $_wp_sidebars_widgets   = array();
+               update_option( 'sidebars_widgets', array() );
+       }
+
+       private function setup_widget( $option_name, $number, $settings ) {
+               update_option(
+                       $option_name,
+                       array(
+                               $number => $settings,
+                       )
+               );
+       }
+
+       private function setup_sidebar( $id, $attrs = array(), $widgets = array() ) {
+               global $wp_registered_sidebars;
+               update_option(
+                       'sidebars_widgets',
+                       array(
+                               $id => $widgets,
+                       )
+               );
+               $wp_registered_sidebars[ $id ] = array_merge(
+                       array(
+                               'id'            => $id,
+                               'before_widget' => '',
+                               'after_widget'  => '',
+                               'before_title'  => '',
+                               'after_title'   => '',
+                       ),
+                       $attrs
+               );
+
+               global $wp_registered_widgets;
+               foreach ( $wp_registered_widgets as $wp_registered_widget ) {
+                       if ( is_array( $wp_registered_widget['callback'] ) ) {
+                               $wp_registered_widget['callback'][0]->_register();
+                       }
+               }
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_register_routes() {
+               $routes = rest_get_server()->get_routes();
+               $this->assertArrayHasKey( '/wp/v2/sidebars', $routes );
+               $this->assertArrayHasKey( '/wp/v2/sidebars/(?P<id>[\w-]+)', $routes );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_context_param() {
+               // Collection.
+               $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertSame( 'view', $data['endpoints'][0]['args']['context']['default'] );
+               $this->assertSame( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+               // Single.
+               $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/sidebars/sidebar-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertSame( 'view', $data['endpoints'][0]['args']['context']['default'] );
+               $this->assertSame( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items() {
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEquals( array(), $data );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_no_permission() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_basic_sidebar() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               array(
+                                       'id'            => 'sidebar-1',
+                                       'name'          => 'Test sidebar',
+                                       'description'   => '',
+                                       'status'        => 'active',
+                                       'widgets'       => array(),
+                                       'class'         => '',
+                                       'before_widget' => '',
+                                       'after_widget'  => '',
+                                       'before_title'  => '',
+                                       'after_title'   => '',
+                               ),
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_active_sidebar_with_widgets() {
+               $this->setup_widget(
+                       'widget_rss',
+                       1,
+                       array(
+                               'title' => 'RSS test',
+                       )
+               );
+               $this->setup_widget(
+                       'widget_text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               array(
+                                       'id'            => 'sidebar-1',
+                                       'name'          => 'Test sidebar',
+                                       'description'   => '',
+                                       'status'        => 'active',
+                                       'widgets'       => array(
+                                               'text-1',
+                                               'rss-1',
+                                       ),
+                                       'class'         => '',
+                                       'before_widget' => '',
+                                       'after_widget'  => '',
+                                       'before_title'  => '',
+                                       'after_title'   => '',
+                               ),
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars/sidebar-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               'id'            => 'sidebar-1',
+                               'name'          => 'Test sidebar',
+                               'description'   => '',
+                               'status'        => 'active',
+                               'widgets'       => array(),
+                               'class'         => '',
+                               'before_widget' => '',
+                               'after_widget'  => '',
+                               'before_title'  => '',
+                               'after_title'   => '',
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_no_permission() {
+               wp_set_current_user( 0 );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars/sidebar-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/sidebars/sidebar-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * The test_create_item() method does not exist for sidebar.
+        */
+       public function test_create_item() {
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item() {
+               $this->setup_widget(
+                       'widget_rss',
+                       1,
+                       array(
+                               'title' => 'RSS test',
+                       )
+               );
+               $this->setup_widget(
+                       'widget_text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_widget(
+                       'widget_text',
+                       2,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(
+                                       'text-1',
+                                       'text-2',
+                               ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               'id'            => 'sidebar-1',
+                               'name'          => 'Test sidebar',
+                               'description'   => '',
+                               'status'        => 'active',
+                               'widgets'       => array(
+                                       'text-1',
+                                       'text-2',
+                               ),
+                               'class'         => '',
+                               'before_widget' => '',
+                               'after_widget'  => '',
+                               'before_title'  => '',
+                               'after_title'   => '',
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_removes_widget_from_existing_sidebar() {
+               $this->setup_widget(
+                       'widget_text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+               $this->setup_sidebar(
+                       'sidebar-2',
+                       array(
+                               'name' => 'Test sidebar 2',
+                       ),
+                       array()
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/sidebars/sidebar-2' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(
+                                       'text-1',
+                               ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertContains( 'text-1', $data['widgets'] );
+
+               $this->assertNotContains( 'text-1', rest_do_request( '/wp/v2/sidebars/sidebar-1' )->get_data()['widgets'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_moves_omitted_widget_to_inactive_sidebar() {
+               $this->setup_widget(
+                       'widget_text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_widget(
+                       'widget_text',
+                       2,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(
+                                       'text-2',
+                               ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertContains( 'text-2', $data['widgets'] );
+               $this->assertNotContains( 'text-1', $data['widgets'] );
+
+               $this->assertContains( 'text-1', rest_do_request( '/wp/v2/sidebars/wp_inactive_widgets' )->get_data()['widgets'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_inactive_widgets() {
+               $this->setup_widget(
+                       'widget_rss',
+                       1,
+                       array(
+                               'title' => 'RSS test',
+                       )
+               );
+               $this->setup_widget(
+                       'widget_text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+               update_option(
+                       'sidebars_widgets',
+                       array_merge(
+                               get_option( 'sidebars_widgets' ),
+                               array(
+                                       'wp_inactive_widgets' => array( 'rss-1', 'rss' ),
+                               )
+                       )
+               );
+
+               $request = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
+               $request->set_param( 'context', 'view' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               array(
+                                       'id'            => 'sidebar-1',
+                                       'name'          => 'Test sidebar',
+                                       'description'   => '',
+                                       'status'        => 'active',
+                                       'widgets'       => array(
+                                               'text-1',
+                                       ),
+                                       'class'         => '',
+                                       'before_widget' => '',
+                                       'after_widget'  => '',
+                                       'before_title'  => '',
+                                       'after_title'   => '',
+                               ),
+                               array(
+                                       'id'            => 'wp_inactive_widgets',
+                                       'name'          => 'Inactive widgets',
+                                       'description'   => '',
+                                       'status'        => 'inactive',
+                                       'widgets'       => array(
+                                               'rss-1',
+                                       ),
+                                       'class'         => '',
+                                       'before_widget' => '',
+                                       'after_widget'  => '',
+                                       'before_title'  => '',
+                                       'after_title'   => '',
+                               ),
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_no_permission() {
+               wp_set_current_user( 0 );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * The test_delete_item() method does not exist for sidebar.
+        */
+       public function test_delete_item() {
+       }
+
+       /**
+        * The test_prepare_item() method does not exist for sidebar.
+        */
+       public function test_prepare_item() {
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_schema() {
+               wp_set_current_user( self::$admin_id );
+               $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/sidebars' );
+               $response   = rest_get_server()->dispatch( $request );
+               $data       = $response->get_data();
+               $properties = $data['schema']['properties'];
+
+               $this->assertArrayHasKey( 'id', $properties );
+               $this->assertArrayHasKey( 'name', $properties );
+               $this->assertArrayHasKey( 'description', $properties );
+               $this->assertArrayHasKey( 'status', $properties );
+               $this->assertArrayHasKey( 'widgets', $properties );
+               $this->assertArrayHasKey( 'class', $properties );
+               $this->assertArrayHasKey( 'before_widget', $properties );
+               $this->assertArrayHasKey( 'after_widget', $properties );
+               $this->assertArrayHasKey( 'before_title', $properties );
+               $this->assertArrayHasKey( 'after_title', $properties );
+               $this->assertCount( 10, $properties );
+       }
+
+       /**
+        * Helper to remove links key.
+        *
+        * @param array $data Array of data.
+        *
+        * @return array
+        */
+       protected function remove_links( $data ) {
+               if ( ! is_array( $data ) ) {
+                       return $data;
+               }
+               $count = 0;
+               foreach ( $data as $item ) {
+                       if ( isset( $item['_links'] ) ) {
+                               unset( $data[ $count ]['_links'] );
+                       }
+                       $count ++;
+               }
+
+               return $data;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/rest-sidebars-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestwidgettypescontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/rest-widget-types-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-widget-types-controller.php                               (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/rest-widget-types-controller.php 2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,464 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Unit tests covering WP_Test_REST_Widget_Types_Controller functionality.
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Tests for WP_REST_Widget_Types_Controller.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_TEST_REST_Controller_Testcase
+ * @group restapi
+ * @covers WP_REST_Widget_Types_Controller
+ */
+class WP_Test_REST_Widget_Types_Controller extends WP_Test_REST_Controller_Testcase {
+
+       /**
+        * Admin user ID.
+        *
+        * @since 5.8.0
+        *
+        * @var int $subscriber_id
+        */
+       protected static $admin_id;
+
+       /**
+        * Subscriber user ID.
+        *
+        * @since 5.8.0
+        *
+        * @var int $subscriber_id
+        */
+       protected static $subscriber_id;
+
+       /**
+        * Create fake data before our tests run.
+        *
+        * @since 5.8.0
+        *
+        * @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',
+                       )
+               );
+       }
+
+       public static function wpTearDownAfterClass() {
+               self::delete_user( self::$admin_id );
+               self::delete_user( self::$subscriber_id );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_register_routes() {
+               $routes = rest_get_server()->get_routes();
+               $this->assertArrayHasKey( '/wp/v2/widget-types', $routes );
+               $this->assertCount( 1, $routes['/wp/v2/widget-types'] );
+               $this->assertArrayHasKey( '/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)', $routes );
+               $this->assertCount( 1, $routes['/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)'] );
+               $this->assertArrayHasKey( '/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)/encode', $routes );
+               $this->assertCount( 1, $routes['/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)/encode'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_context_param() {
+               // Collection.
+               $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/widget-types' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertSame( 'view', $data['endpoints'][0]['args']['context']['default'] );
+               $this->assertSame( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+               // Single.
+               $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/widget-types/calendar' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertSame( 'view', $data['endpoints'][0]['args']['context']['default'] );
+               $this->assertSame( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertGreaterThan( 1, count( $data ) );
+               $endpoint = new WP_REST_Widget_Types_Controller;
+               foreach ( $data as $item ) {
+                       $widget_type = $endpoint->get_widget( $item['name'] );
+                       $this->check_widget_type_object( $widget_type, $item, $item['_links'] );
+               }
+
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item() {
+               $widget_name = 'calendar';
+               wp_set_current_user( self::$admin_id );
+               $request     = new WP_REST_Request( 'GET', '/wp/v2/widget-types/' . $widget_name );
+               $response    = rest_get_server()->dispatch( $request );
+               $endpoint    = new WP_REST_Widget_Types_Controller;
+               $widget_type = $endpoint->get_widget( $widget_name );
+               $this->check_widget_type_object( $widget_type, $response->get_data(), $response->get_links() );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_widget_legacy() {
+               $widget_id = 'legacy';
+               wp_register_sidebar_widget(
+                       $widget_id,
+                       'WP legacy widget',
+                       function() {}
+               );
+               wp_set_current_user( self::$admin_id );
+               $request     = new WP_REST_Request( 'GET', '/wp/v2/widget-types/' . $widget_id );
+               $response    = rest_get_server()->dispatch( $request );
+               $endpoint    = new WP_REST_Widget_Types_Controller;
+               $widget_type = $endpoint->get_widget( $widget_id );
+               $this->check_widget_type_object( $widget_type, $response->get_data(), $response->get_links() );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_widget_invalid_name() {
+               $widget_type = 'fake';
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types/' . $widget_type );
+               $response = rest_get_server()->dispatch( $request );
+
+               $this->assertErrorResponse( 'rest_widget_type_invalid', $response, 404 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_schema() {
+               wp_set_current_user( self::$admin_id );
+               $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/widget-types' );
+               $response   = rest_get_server()->dispatch( $request );
+               $data       = $response->get_data();
+               $properties = $data['schema']['properties'];
+               $this->assertCount( 5, $properties );
+
+               $this->assertArrayHasKey( 'name', $properties );
+               $this->assertArrayHasKey( 'id', $properties );
+               $this->assertArrayHasKey( 'description', $properties );
+               $this->assertArrayHasKey( 'is_multi', $properties );
+               $this->assertArrayHasKey( 'classname', $properties );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_wrong_permission() {
+               wp_set_current_user( self::$subscriber_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_wrong_permission() {
+               wp_set_current_user( self::$subscriber_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types/calendar' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_no_permission() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_no_permission() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widget-types/calendar' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_prepare_item() {
+               $endpoint    = new WP_REST_Widget_Types_Controller;
+               $widget_type = $endpoint->get_widget( 'calendar' );
+               $request     = new WP_REST_Request;
+               $request->set_param( 'context', 'edit' );
+               $response = $endpoint->prepare_item_for_response( $widget_type, $request );
+               $this->check_widget_type_object( $widget_type, $response->get_data(), $response->get_links() );
+       }
+
+       /**
+        * Util check widget type object against.
+        *
+        * @since 5.8.0
+        *
+        * @param array $widget_type Sample widget type.
+        * @param array $data Data to compare against.
+        * @param array $links Links to compare again.
+        */
+       protected function check_widget_type_object( $widget_type, $data, $links ) {
+               // Test data.
+               $extra_fields = array(
+                       'name',
+                       'id_base',
+                       'option_name',
+                       'control_options',
+                       'widget_options',
+                       'widget_class',
+                       'is_multi',
+               );
+
+               foreach ( $extra_fields as $extra_field ) {
+                       if ( isset( $widget_type->$extra_field ) ) {
+                               $this->assertSame( $data[ $extra_field ], $widget_type->$extra_field, 'Field ' . $extra_field );
+                       }
+               }
+
+               // Test links.
+               $this->assertSame( rest_url( 'wp/v2/widget-types' ), $links['collection'][0]['href'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_encode_form_data_with_no_input() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'POST', '/wp/v2/widget-types/search/encode' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals(
+                       "<p>\n" .
+                       "\t\t\t<label for=\"widget-search--1-title\">Title:</label>\n" .
+                       "\t\t\t<input class=\"widefat\" id=\"widget-search--1-title\" name=\"widget-search[-1][title]\" type=\"text\" value=\"\" />\n" .
+                       "\t\t</p>",
+                       $data['form']
+               );
+               $this->assertStringMatchesFormat(
+                       "<div class=\"widget widget_search\"><form role=\"search\" method=\"get\" id=\"searchform\" class=\"searchform\" action=\"%s\">\n" .
+                       "\t\t\t\t<div>\n" .
+                       "\t\t\t\t\t<label class=\"screen-reader-text\" for=\"s\">Search for:</label>\n" .
+                       "\t\t\t\t\t<input type=\"text\" value=\"\" name=\"s\" id=\"s\" />\n" .
+                       "\t\t\t\t\t<input type=\"submit\" id=\"searchsubmit\" value=\"Search\" />\n" .
+                       "\t\t\t\t</div>\n" .
+                       "\t\t\t</form></div>",
+                       $data['preview']
+               );
+               $this->assertEqualSets(
+                       array(
+                               'encoded' => base64_encode( serialize( array() ) ),
+                               'hash'    => wp_hash( serialize( array() ) ),
+                               'raw'     => new stdClass,
+                       ),
+                       $data['instance']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_encode_form_data_with_number() {
+               wp_set_current_user( self::$admin_id );
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widget-types/search/encode' );
+               $request->set_param( 'number', 8 );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals(
+                       "<p>\n" .
+                       "\t\t\t<label for=\"widget-search-8-title\">Title:</label>\n" .
+                       "\t\t\t<input class=\"widefat\" id=\"widget-search-8-title\" name=\"widget-search[8][title]\" type=\"text\" value=\"\" />\n" .
+                       "\t\t</p>",
+                       $data['form']
+               );
+               $this->assertStringMatchesFormat(
+                       "<div class=\"widget widget_search\"><form role=\"search\" method=\"get\" id=\"searchform\" class=\"searchform\" action=\"%s\">\n" .
+                       "\t\t\t\t<div>\n" .
+                       "\t\t\t\t\t<label class=\"screen-reader-text\" for=\"s\">Search for:</label>\n" .
+                       "\t\t\t\t\t<input type=\"text\" value=\"\" name=\"s\" id=\"s\" />\n" .
+                       "\t\t\t\t\t<input type=\"submit\" id=\"searchsubmit\" value=\"Search\" />\n" .
+                       "\t\t\t\t</div>\n" .
+                       "\t\t\t</form></div>",
+                       $data['preview']
+               );
+               $this->assertEqualSets(
+                       array(
+                               'encoded' => base64_encode( serialize( array() ) ),
+                               'hash'    => wp_hash( serialize( array() ) ),
+                               'raw'     => new stdClass,
+                       ),
+                       $data['instance']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_encode_form_data_with_instance() {
+               wp_set_current_user( self::$admin_id );
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widget-types/search/encode' );
+               $request->set_param(
+                       'instance',
+                       array(
+                               'encoded' => base64_encode( serialize( array( 'title' => 'Test title' ) ) ),
+                               'hash'    => wp_hash( serialize( array( 'title' => 'Test title' ) ) ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals(
+                       "<p>\n" .
+                       "\t\t\t<label for=\"widget-search--1-title\">Title:</label>\n" .
+                       "\t\t\t<input class=\"widefat\" id=\"widget-search--1-title\" name=\"widget-search[-1][title]\" type=\"text\" value=\"Test title\" />\n" .
+                       "\t\t</p>",
+                       $data['form']
+               );
+               $this->assertStringMatchesFormat(
+                       "<div class=\"widget widget_search\"><h2 class=\"widgettitle\">Test title</h2><form role=\"search\" method=\"get\" id=\"searchform\" class=\"searchform\" action=\"%s\">\n" .
+                       "\t\t\t\t<div>\n" .
+                       "\t\t\t\t\t<label class=\"screen-reader-text\" for=\"s\">Search for:</label>\n" .
+                       "\t\t\t\t\t<input type=\"text\" value=\"\" name=\"s\" id=\"s\" />\n" .
+                       "\t\t\t\t\t<input type=\"submit\" id=\"searchsubmit\" value=\"Search\" />\n" .
+                       "\t\t\t\t</div>\n" .
+                       "\t\t\t</form></div>",
+                       $data['preview']
+               );
+               $this->assertEqualSets(
+                       array(
+                               'encoded' => base64_encode( serialize( array( 'title' => 'Test title' ) ) ),
+                               'hash'    => wp_hash( serialize( array( 'title' => 'Test title' ) ) ),
+                               'raw'     => array( 'title' => 'Test title' ),
+                       ),
+                       $data['instance']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_encode_form_data_with_form_data() {
+               wp_set_current_user( self::$admin_id );
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widget-types/search/encode' );
+               $request->set_param( 'form_data', 'widget-search[-1][title]=Updated+title' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals(
+                       "<p>\n" .
+                       "\t\t\t<label for=\"widget-search--1-title\">Title:</label>\n" .
+                       "\t\t\t<input class=\"widefat\" id=\"widget-search--1-title\" name=\"widget-search[-1][title]\" type=\"text\" value=\"Updated title\" />\n" .
+                       "\t\t</p>",
+                       $data['form']
+               );
+               $this->assertStringMatchesFormat(
+                       "<div class=\"widget widget_search\"><h2 class=\"widgettitle\">Updated title</h2><form role=\"search\" method=\"get\" id=\"searchform\" class=\"searchform\" action=\"%s\">\n" .
+                       "\t\t\t\t<div>\n" .
+                       "\t\t\t\t\t<label class=\"screen-reader-text\" for=\"s\">Search for:</label>\n" .
+                       "\t\t\t\t\t<input type=\"text\" value=\"\" name=\"s\" id=\"s\" />\n" .
+                       "\t\t\t\t\t<input type=\"submit\" id=\"searchsubmit\" value=\"Search\" />\n" .
+                       "\t\t\t\t</div>\n" .
+                       "\t\t\t</form></div>",
+                       $data['preview']
+               );
+               $this->assertEqualSets(
+                       array(
+                               'encoded' => base64_encode( serialize( array( 'title' => 'Updated title' ) ) ),
+                               'hash'    => wp_hash( serialize( array( 'title' => 'Updated title' ) ) ),
+                               'raw'     => array( 'title' => 'Updated title' ),
+                       ),
+                       $data['instance']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_encode_form_data_no_raw() {
+               global $wp_widget_factory;
+               wp_set_current_user( self::$admin_id );
+               $wp_widget_factory->widgets['WP_Widget_Search']->widget_options['show_instance_in_rest'] = false;
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widget-types/search/encode' );
+               $request->set_param(
+                       'instance',
+                       array(
+                               'encoded' => base64_encode( serialize( array( 'title' => 'Test title' ) ) ),
+                               'hash'    => wp_hash( serialize( array( 'title' => 'Test title' ) ) ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals(
+                       "<p>\n" .
+                       "\t\t\t<label for=\"widget-search--1-title\">Title:</label>\n" .
+                       "\t\t\t<input class=\"widefat\" id=\"widget-search--1-title\" name=\"widget-search[-1][title]\" type=\"text\" value=\"Test title\" />\n" .
+                       "\t\t</p>",
+                       $data['form']
+               );
+               $this->assertStringMatchesFormat(
+                       "<div class=\"widget widget_search\"><h2 class=\"widgettitle\">Test title</h2><form role=\"search\" method=\"get\" id=\"searchform\" class=\"searchform\" action=\"%s\">\n" .
+                       "\t\t\t\t<div>\n" .
+                       "\t\t\t\t\t<label class=\"screen-reader-text\" for=\"s\">Search for:</label>\n" .
+                       "\t\t\t\t\t<input type=\"text\" value=\"\" name=\"s\" id=\"s\" />\n" .
+                       "\t\t\t\t\t<input type=\"submit\" id=\"searchsubmit\" value=\"Search\" />\n" .
+                       "\t\t\t\t</div>\n" .
+                       "\t\t\t</form></div>",
+                       $data['preview']
+               );
+               $this->assertEqualSets(
+                       array(
+                               'encoded' => base64_encode( serialize( array( 'title' => 'Test title' ) ) ),
+                               'hash'    => wp_hash( serialize( array( 'title' => 'Test title' ) ) ),
+                       ),
+                       $data['instance']
+               );
+               $wp_widget_factory->widgets['WP_Widget_Search']->widget_options['show_instance_in_rest'] = true;
+       }
+
+
+       /**
+        * The test_create_item() method does not exist for widget types.
+        */
+       public function test_create_item() {}
+
+       /**
+        * The test_update_item() method does not exist for widget types.
+        */
+       public function test_update_item() {}
+
+       /**
+        * The test_delete_item() method does not exist for widget types.
+        */
+       public function test_delete_item() {}
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/rest-widget-types-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestwidgetscontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/rest-widgets-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-widgets-controller.php                            (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/rest-widgets-controller.php      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,1388 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Unit tests covering WP_REST_Widgets_Controller_Test functionality.
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Tests for REST API for Widgets.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_Test_REST_Controller_Testcase
+ * @group restapi
+ * @covers WP_REST_Widgets_Controller
+ */
+class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
+       /**
+        * @var int
+        */
+       public $menu_id;
+
+       /**
+        * @var int
+        */
+       protected static $superadmin_id;
+
+       /**
+        * @var int
+        */
+       protected static $admin_id;
+
+       /**
+        * @var int
+        */
+       protected static $admin_id_without_unfiltered_html;
+
+       /**
+        * @var int
+        */
+       protected static $editor_id;
+
+       /**
+        * @var int
+        */
+       protected static $subscriber_id;
+
+       /**
+        * @var int
+        */
+       protected static $author_id;
+
+       /**
+        * @var int
+        */
+       protected static $per_page = 50;
+
+       /**
+        * Create fake data before our tests run.
+        *
+        * @param WP_UnitTest_Factory $factory Helper that lets us create fake data.
+        */
+       public static function wpSetUpBeforeClass( $factory ) {
+               self::$superadmin_id = $factory->user->create(
+                       array(
+                               'role'       => 'administrator',
+                               'user_login' => 'superadmin',
+                       )
+               );
+               if ( is_multisite() ) {
+                       update_site_option( 'site_admins', array( 'superadmin' ) );
+               }
+               self::$admin_id      = $factory->user->create(
+                       array(
+                               'role' => 'administrator',
+                       )
+               );
+               self::$editor_id     = $factory->user->create(
+                       array(
+                               'role' => 'editor',
+                       )
+               );
+               self::$author_id     = $factory->user->create(
+                       array(
+                               'role' => 'author',
+                       )
+               );
+               self::$subscriber_id = $factory->user->create(
+                       array(
+                               'role' => 'subscriber',
+                       )
+               );
+       }
+
+       public function setUp() {
+               global $wp_registered_widgets, $wp_registered_sidebars, $_wp_sidebars_widgets, $wp_widget_factory;
+
+               parent::setUp();
+
+               wp_set_current_user( self::$admin_id );
+
+               // Unregister all widgets and sidebars.
+               $wp_registered_widgets  = array();
+               $wp_registered_sidebars = array();
+               $_wp_sidebars_widgets   = array();
+               update_option( 'sidebars_widgets', array() );
+
+               // Re-register core widgets.
+               $wp_widget_factory->_register_widgets();
+
+               // Register a non-multi widget for testing.
+               wp_register_widget_control(
+                       'testwidget',
+                       'WP test widget',
+                       function () {
+                               $settings = get_option( 'widget_testwidget' );
+
+                               // check if anything's been sent.
+                               if ( isset( $_POST['update_testwidget'] ) ) {
+                                       $settings['id']    = $_POST['test_id'];
+                                       $settings['title'] = $_POST['test_title'];
+
+                                       update_option( 'widget_testwidget', $settings );
+                               }
+
+                               echo 'WP test widget form';
+                       },
+                       100,
+                       200
+               );
+               wp_register_sidebar_widget(
+                       'testwidget',
+                       'WP test widget',
+                       function () {
+                               $settings = wp_parse_args(
+                                       get_option( 'widget_testwidget' ),
+                                       array(
+                                               'id'    => 'Default id',
+                                               'title' => 'Default text',
+                                       )
+                               );
+                               echo '<h1>' . $settings['id'] . '</h1><span>' . $settings['title'] . '</span>';
+                       },
+                       array(
+                               'description' => 'A non-multi widget for testing.',
+                       )
+               );
+       }
+
+       private function setup_widget( $id_base, $number, $settings ) {
+               global $wp_widget_factory;
+
+               $option_name = "widget_$id_base";
+               update_option(
+                       $option_name,
+                       array(
+                               $number => $settings,
+                       )
+               );
+
+               $widget_object = $wp_widget_factory->get_widget_object( $id_base );
+               $widget_object->_set( $number );
+               $widget_object->_register_one( $number );
+       }
+
+       private function setup_sidebar( $id, $attrs = array(), $widgets = array() ) {
+               global $wp_registered_sidebars;
+               update_option(
+                       'sidebars_widgets',
+                       array_merge(
+                               (array) get_option( 'sidebars_widgets', array() ),
+                               array(
+                                       $id => $widgets,
+                               )
+                       )
+               );
+               $wp_registered_sidebars[ $id ] = array_merge(
+                       array(
+                               'id'            => $id,
+                               'before_widget' => '',
+                               'after_widget'  => '',
+                               'before_title'  => '',
+                               'after_title'   => '',
+                       ),
+                       $attrs
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_register_routes() {
+               $routes = rest_get_server()->get_routes();
+               $this->assertArrayHasKey( '/wp/v2/widgets', $routes );
+               $this->assertArrayHasKey( '/wp/v2/widgets/(?P<id>[\w\-]+)', $routes );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_context_param() {
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_no_widgets() {
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEquals( array(), $data );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_no_permission() {
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_items() {
+               global $wp_widget_factory;
+
+               $wp_widget_factory->widgets['WP_Widget_RSS']->widget_options['show_instance_in_rest'] = false;
+
+               $block_content = '<!-- wp:paragraph --><p>Block test</p><!-- /wp:paragraph -->';
+
+               $this->setup_widget(
+                       'rss',
+                       1,
+                       array(
+                               'title' => 'RSS test',
+                       )
+               );
+               $this->setup_widget(
+                       'block',
+                       1,
+                       array(
+                               'content' => $block_content,
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'block-1', 'rss-1', 'testwidget' )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEqualSets(
+                       array(
+                               array(
+                                       'id'       => 'block-1',
+                                       'sidebar'  => 'sidebar-1',
+                                       'instance' => array(
+                                               'encoded' => base64_encode(
+                                                       serialize(
+                                                               array(
+                                                                       'content' => $block_content,
+                                                               )
+                                                       )
+                                               ),
+                                               'hash'    => wp_hash(
+                                                       serialize(
+                                                               array(
+                                                                       'content' => $block_content,
+                                                               )
+                                                       )
+                                               ),
+                                               'raw'     => array(
+                                                       'content' => $block_content,
+                                               ),
+                                       ),
+                                       'id_base'  => 'block',
+                                       'rendered' => '<p>Block test</p>',
+                               ),
+                               array(
+                                       'id'       => 'rss-1',
+                                       'sidebar'  => 'sidebar-1',
+                                       'instance' => array(
+                                               'encoded' => base64_encode(
+                                                       serialize(
+                                                               array(
+                                                                       'title' => 'RSS test',
+                                                               )
+                                                       )
+                                               ),
+                                               'hash'    => wp_hash(
+                                                       serialize(
+                                                               array(
+                                                                       'title' => 'RSS test',
+                                                               )
+                                                       )
+                                               ),
+                                       ),
+                                       'id_base'  => 'rss',
+                                       'rendered' => '',
+                               ),
+                               array(
+                                       'id'       => 'testwidget',
+                                       'sidebar'  => 'sidebar-1',
+                                       'instance' => null,
+                                       'id_base'  => 'testwidget',
+                                       'rendered' => '<h1>Default id</h1><span>Default text</span>',
+                               ),
+                       ),
+                       $data
+               );
+
+               $wp_widget_factory->widgets['WP_Widget_RSS']->widget_options['show_instance_in_rest'] = true;
+       }
+
+       /**
+        * Test a GET request in edit context. In particular, we expect rendered_form to be served correctly.
+        *
+        * @ticket 41683
+        */
+       public function test_get_items_edit_context() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'testwidget' )
+               );
+
+               $request            = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
+               $request['context'] = 'edit';
+               $response           = rest_get_server()->dispatch( $request );
+               $data               = $response->get_data();
+               $data               = $this->remove_links( $data );
+               $this->assertEqualSets(
+                       array(
+                               array(
+                                       'id'            => 'text-1',
+                                       'sidebar'       => 'sidebar-1',
+                                       'instance'      => array(
+                                               'encoded' => base64_encode(
+                                                       serialize(
+                                                               array(
+                                                                       'text' => 'Custom text test',
+                                                               )
+                                                       )
+                                               ),
+                                               'hash'    => wp_hash(
+                                                       serialize(
+                                                               array(
+                                                                       'text' => 'Custom text test',
+                                                               )
+                                                       )
+                                               ),
+                                               'raw'     => array(
+                                                       'text' => 'Custom text test',
+                                               ),
+                                       ),
+                                       'id_base'       => 'text',
+                                       'rendered'      => '<div class="textwidget">Custom text test</div>',
+                                       'rendered_form' => '<input id="widget-text-1-title" name="widget-text[1][title]" class="title sync-input" type="hidden" value="">' . "\n" .
+                                                                       '                       <textarea id="widget-text-1-text" name="widget-text[1][text]" class="text sync-input" hidden>Custom text test</textarea>' . "\n" .
+                                                                       '                       <input id="widget-text-1-filter" name="widget-text[1][filter]" class="filter sync-input" type="hidden" value="on">' . "\n" .
+                                                                       '                       <input id="widget-text-1-visual" name="widget-text[1][visual]" class="visual sync-input" type="hidden" value="on">',
+                               ),
+                               array(
+                                       'id'            => 'testwidget',
+                                       'sidebar'       => 'sidebar-1',
+                                       'instance'      => null,
+                                       'id_base'       => 'testwidget',
+                                       'rendered'      => '<h1>Default id</h1><span>Default text</span>',
+                                       'rendered_form' => 'WP test widget form',
+                               ),
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEqualSets(
+                       array(
+                               'id'       => 'text-1',
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'encoded' => base64_encode(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Custom text test',
+                                                       )
+                                               )
+                                       ),
+                                       'hash'    => wp_hash(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Custom text test',
+                                                       )
+                                               )
+                                       ),
+                                       'raw'     => array(
+                                               'text' => 'Custom text test',
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                               'rendered' => '<div class="textwidget">Custom text test</div>',
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_no_permission() {
+               wp_set_current_user( 0 );
+
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'encoded' => base64_encode(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Updated text test',
+                                                       )
+                                               )
+                                       ),
+                                       'hash'    => wp_hash(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Updated text test',
+                                                       )
+                                               )
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'text-2', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       get_option( 'widget_text' )[2]
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_malformed_instance() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'encoded' => base64_encode(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Updated text test',
+                                                       )
+                                               )
+                                       ),
+                                       'hash'    => 'badhash',
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_invalid_widget', $response, 400 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_bad_instance() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_invalid_widget', $response, 400 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_using_raw_instance() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'content' => '<!-- wp:paragraph --><p>Block test</p><!-- /wp:paragraph -->',
+                                       ),
+                               ),
+                               'id_base'  => 'block',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'block-2', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'content' => '<!-- wp:paragraph --><p>Block test</p><!-- /wp:paragraph -->',
+                       ),
+                       get_option( 'widget_block' )[2]
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_raw_instance_not_supported() {
+               global $wp_widget_factory;
+
+               $wp_widget_factory->widgets['WP_Widget_Text']->widget_options['show_instance_in_rest'] = false;
+
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'title' => 'Updated text test',
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_invalid_widget', $response, 400 );
+
+               $wp_widget_factory->widgets['WP_Widget_Text']->widget_options['show_instance_in_rest'] = true;
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_using_form_data() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'   => 'sidebar-1',
+                               'form_data' => 'widget-text[2][text]=Updated+text+test',
+                               'id_base'   => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'text-2', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_multiple_in_a_row() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array( 'text' => 'Text 1' ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'text-2', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Text 1',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array( 'text' => 'Text 2' ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'text-3', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Text 2',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+
+               $sidebar = rest_do_request( '/wp/v2/sidebars/sidebar-1' );
+               $this->assertContains( 'text-2', $sidebar->get_data()['widgets'] );
+               $this->assertContains( 'text-3', $sidebar->get_data()['widgets'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_second_instance() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       )
+               );
+
+               $request = new WP_REST_Request( 'POST', '/wp/v2/widgets' );
+               $request->set_body_params(
+                       array(
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'text' => 'Updated text test',
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'text-2', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/text-1' );
+               $request->set_body_params(
+                       array(
+                               'id'       => 'text-1',
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'text' => 'Updated text test',
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEquals( 'text-1', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_reassign_sidebar() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+               $this->setup_sidebar(
+                       'sidebar-2',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array()
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/text-1' );
+               $request->set_body_params(
+                       array(
+                               'sidebar' => 'sidebar-2',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $error    = $response->as_error();
+               $this->assertNotWPError( $error, $error ? $error->get_error_message() : '' );
+               $this->assertEquals( 'sidebar-2', $response->get_data()['sidebar'] );
+
+               $sidebar1 = rest_do_request( '/wp/v2/sidebars/sidebar-1' );
+               $this->assertNotContains( 'text-1', $sidebar1->get_data()['widgets'] );
+
+               $sidebar2 = rest_do_request( '/wp/v2/sidebars/sidebar-2' );
+               $this->assertContains( 'text-1', $sidebar2->get_data()['widgets'] );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_shouldnt_require_id_base() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/text-1' );
+               $request->set_body_params(
+                       array(
+                               'id'       => 'text-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'text' => 'Updated text test',
+                                       ),
+                               ),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEquals( 'text-1', $data['id'] );
+               $this->assertEquals( 'sidebar-1', $data['sidebar'] );
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+       }
+
+       /**
+        * @group multisite
+        */
+       public function test_store_html_as_admin() {
+               if ( is_multisite() ) {
+                       $this->assertEquals(
+                               '<div class="textwidget">alert(1)</div>',
+                               $this->update_text_widget_with_raw_html( '<script>alert(1)</script>' )
+                       );
+               } else {
+                       $this->assertEquals(
+                               '<div class="textwidget"><script>alert(1)</script></div>',
+                               $this->update_text_widget_with_raw_html( '<script>alert(1)</script>' )
+                       );
+               }
+       }
+
+       /**
+        * @group multisite
+        */
+       public function test_store_html_as_superadmin() {
+               wp_set_current_user( self::$superadmin_id );
+               if ( is_multisite() ) {
+                       $this->assertEquals(
+                               '<div class="textwidget"><script>alert(1)</script></div>',
+                               $this->update_text_widget_with_raw_html( '<script>alert(1)</script>' )
+                       );
+               } else {
+                       $this->assertEquals(
+                               '<div class="textwidget"><script>alert(1)</script></div>',
+                               $this->update_text_widget_with_raw_html( '<script>alert(1)</script>' )
+                       );
+               }
+       }
+
+       protected function update_text_widget_with_raw_html( $html ) {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/text-1' );
+               $request->set_body_params(
+                       array(
+                               'id'       => 'text-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'text' => $html,
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               return $data['rendered'];
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_legacy_widget() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'testwidget' )
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/testwidget' );
+               $request->set_body_params(
+                       array(
+                               'id'        => 'testwidget',
+                               'name'      => 'WP test widget',
+                               'form_data' => 'test_id=My+test+id&test_title=My+test+title&update_testwidget=true',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               'id'            => 'testwidget',
+                               'sidebar'       => 'sidebar-1',
+                               'instance'      => null,
+                               'rendered'      => '<h1>My test id</h1><span>My test title</span>',
+                               'rendered_form' => 'WP test widget form',
+                               'id_base'       => 'testwidget',
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_create_item_legacy_widget() {
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array()
+               );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/testwidget' );
+               $request->set_body_params(
+                       array(
+                               'id'        => 'testwidget',
+                               'sidebar'   => 'sidebar-1',
+                               'name'      => 'WP test widget',
+                               'form_data' => 'test_id=My+test+id&test_title=My+test+title&update_testwidget=true',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $data     = $this->remove_links( $data );
+               $this->assertEquals(
+                       array(
+                               'id'            => 'testwidget',
+                               'sidebar'       => 'sidebar-1',
+                               'instance'      => null,
+                               'rendered'      => '<h1>My test id</h1><span>My test title</span>',
+                               'rendered_form' => 'WP test widget form',
+                               'id_base'       => 'testwidget',
+                       ),
+                       $data
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_no_permission() {
+               wp_set_current_user( 0 );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_update_item_wrong_permission_author() {
+               wp_set_current_user( self::$author_id );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/sidebars/sidebar-1' );
+               $request->set_body_params(
+                       array(
+                               'widgets' => array(),
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * Tests if the endpoint correctly handles "slashable" characters such as " or '.
+        */
+       public function test_update_item_slashing() {
+               $this->setup_widget( 'text', 1, array( 'text' => 'Custom text test' ) );
+               $this->setup_sidebar( 'sidebar-1', array( 'name' => 'Test sidebar' ), array( 'text-1', 'rss-1' ) );
+
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/widgets/text-1' );
+               $request->set_body_params(
+                       array(
+                               'id'       => 'text-1',
+                               'sidebar'  => 'sidebar-1',
+                               'instance' => array(
+                                       'raw' => array(
+                                               'text' => 'Updated \\" \\\' text test',
+                                       ),
+                               ),
+                               'id_base'  => 'text',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEqualSets(
+                       array(
+                               'text'   => 'Updated \\" \\\' text test',
+                               'title'  => '',
+                               'filter' => false,
+                       ),
+                       $data['instance']['raw']
+               );
+
+               $this->assertEquals(
+                       '<div class="textwidget">Updated \\" \\\' text test</div>',
+                       $data['rendered']
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_delete_item() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request  = new WP_REST_Request( 'DELETE', '/wp/v2/widgets/text-1' );
+               $response = rest_do_request( $request );
+
+               $this->assertEqualSets(
+                       array(
+                               'id'            => 'text-1',
+                               'sidebar'       => 'wp_inactive_widgets',
+                               'instance'      => array(
+                                       'encoded' => base64_encode(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Custom text test',
+                                                       )
+                                               )
+                                       ),
+                                       'hash'    => wp_hash(
+                                               serialize(
+                                                       array(
+                                                               'text' => 'Custom text test',
+                                                       )
+                                               )
+                                       ),
+                                       'raw'     => array(
+                                               'text' => 'Custom text test',
+                                       ),
+                               ),
+                               'id_base'       => 'text',
+                               'rendered'      => '',
+                               'rendered_form' => '<input id="widget-text-1-title" name="widget-text[1][title]" class="title sync-input" type="hidden" value="">' . "\n" .
+                                                               '                       <textarea id="widget-text-1-text" name="widget-text[1][text]" class="text sync-input" hidden>Custom text test</textarea>' . "\n" .
+                                                               '                       <input id="widget-text-1-filter" name="widget-text[1][filter]" class="filter sync-input" type="hidden" value="on">' . "\n" .
+                                                               '                       <input id="widget-text-1-visual" name="widget-text[1][visual]" class="visual sync-input" type="hidden" value="on">',
+                       ),
+                       $response->get_data()
+               );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_delete_item_force() {
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request = new WP_REST_Request( 'DELETE', '/wp/v2/widgets/text-1' );
+               $request->set_query_params( array( 'force' => true ) );
+               $response = rest_do_request( $request );
+
+               $this->assertEqualSets(
+                       array(
+                               'deleted'  => true,
+                               'previous' => array(
+
+                                       'id'            => 'text-1',
+                                       'sidebar'       => 'sidebar-1',
+                                       'instance'      => array(
+                                               'encoded' => base64_encode(
+                                                       serialize(
+                                                               array(
+                                                                       'text' => 'Custom text test',
+                                                               )
+                                                       )
+                                               ),
+                                               'hash'    => wp_hash(
+                                                       serialize(
+                                                               array(
+                                                                       'text' => 'Custom text test',
+                                                               )
+                                                       )
+                                               ),
+                                               'raw'     => array(
+                                                       'text' => 'Custom text test',
+                                               ),
+                                       ),
+                                       'id_base'       => 'text',
+                                       'rendered'      => '<div class="textwidget">Custom text test</div>',
+                                       'rendered_form' => '<input id="widget-text-1-title" name="widget-text[1][title]" class="title sync-input" type="hidden" value="">' . "\n" .
+                                                                       '                       <textarea id="widget-text-1-text" name="widget-text[1][text]" class="text sync-input" hidden>Custom text test</textarea>' . "\n" .
+                                                                       '                       <input id="widget-text-1-filter" name="widget-text[1][filter]" class="filter sync-input" type="hidden" value="on">' . "\n" .
+                                                                       '                       <input id="widget-text-1-visual" name="widget-text[1][visual]" class="visual sync-input" type="hidden" value="on">',
+
+                               ),
+                       ),
+                       $response->get_data()
+               );
+
+               $response = rest_do_request( '/wp/v2/widgets/text-1' );
+               $this->assertEquals( 404, $response->get_status() );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_delete_item_logged_out() {
+               wp_set_current_user( 0 );
+
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request  = new WP_REST_Request( 'DELETE', '/wp/v2/widgets/text-1' );
+               $response = rest_do_request( $request );
+
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_delete_item_author() {
+               wp_set_current_user( self::$author_id );
+
+               $this->setup_widget(
+                       'text',
+                       1,
+                       array(
+                               'text' => 'Custom text test',
+                       )
+               );
+               $this->setup_sidebar(
+                       'sidebar-1',
+                       array(
+                               'name' => 'Test sidebar',
+                       ),
+                       array( 'text-1', 'rss-1' )
+               );
+
+               $request  = new WP_REST_Request( 'DELETE', '/wp/v2/widgets/text-1' );
+               $response = rest_do_request( $request );
+
+               $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
+       }
+
+       /**
+        * The test_prepare_item() method does not exist for sidebar.
+        */
+       public function test_prepare_item() {
+       }
+
+       /**
+        * @ticket 41683
+        */
+       public function test_get_item_schema() {
+               wp_set_current_user( self::$admin_id );
+               $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/widgets' );
+               $response   = rest_get_server()->dispatch( $request );
+               $data       = $response->get_data();
+               $properties = $data['schema']['properties'];
+
+               $this->assertEquals( 7, count( $properties ) );
+               $this->assertArrayHasKey( 'id', $properties );
+               $this->assertArrayHasKey( 'id_base', $properties );
+               $this->assertArrayHasKey( 'sidebar', $properties );
+               $this->assertArrayHasKey( 'rendered', $properties );
+               $this->assertArrayHasKey( 'rendered_form', $properties );
+               $this->assertArrayHasKey( 'instance', $properties );
+               $this->assertArrayHasKey( 'form_data', $properties );
+       }
+
+       /**
+        * Helper to remove links key.
+        *
+        * @param array $data Array of data.
+        *
+        * @return array
+        */
+       protected function remove_links( $data ) {
+               if ( ! is_array( $data ) ) {
+                       return $data;
+               }
+               $count = 0;
+               foreach ( $data as $item ) {
+                       if ( is_array( $item ) && isset( $item['_links'] ) ) {
+                               unset( $data[ $count ]['_links'] );
+                       }
+                       $count ++;
+               }
+
+               return $data;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/rest-widgets-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsqunitfixtureswpapigeneratedjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/fixtures/wp-api-generated.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/fixtures/wp-api-generated.js    2021-05-25 08:21:03 UTC (rev 50994)
+++ trunk/tests/qunit/fixtures/wp-api-generated.js      2021-05-25 08:26:21 UTC (rev 50995)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6416,6 +6416,393 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">             ]
</span><span class="cx" style="display: block; padding: 0 10px">         },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        "/wp/v2/sidebars": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        }
+                    }
+                }
+            ],
+            "_links": {
+                "self": [
+                    {
+                        "href": "http://example.org/index.php?rest_route=/wp/v2/sidebars"
+                    }
+                ]
+            }
+        },
+        "/wp/v2/sidebars/(?P<id>[\\w-]+)": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET",
+                "POST",
+                "PUT",
+                "PATCH"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "id": {
+                            "description": "The id of a registered sidebar",
+                            "type": "string",
+                            "required": false
+                        },
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        }
+                    }
+                },
+                {
+                    "methods": [
+                        "POST",
+                        "PUT",
+                        "PATCH"
+                    ],
+                    "args": {
+                        "widgets": {
+                            "description": "Nested widgets.",
+                            "type": "array",
+                            "items": {
+                                "type": [
+                                    "object",
+                                    "string"
+                                ]
+                            },
+                            "required": false
+                        }
+                    }
+                }
+            ]
+        },
+        "/wp/v2/widget-types": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        }
+                    }
+                }
+            ],
+            "_links": {
+                "self": [
+                    {
+                        "href": "http://example.org/index.php?rest_route=/wp/v2/widget-types"
+                    }
+                ]
+            }
+        },
+        "/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "id": {
+                            "description": "The widget type id.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        }
+                    }
+                }
+            ]
+        },
+        "/wp/v2/widget-types/(?P<id>[a-zA-Z0-9_-]+)/encode": {
+            "namespace": "wp/v2",
+            "methods": [
+                "POST"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "POST"
+                    ],
+                    "args": {
+                        "id": {
+                            "description": "The widget type id.",
+                            "type": "string",
+                            "required": true
+                        },
+                        "instance": {
+                            "description": "Current instance settings of the widget.",
+                            "type": "object",
+                            "required": false
+                        },
+                        "form_data": {
+                            "description": "Serialized widget form data to encode into instance settings.",
+                            "type": "string",
+                            "required": false
+                        }
+                    }
+                }
+            ]
+        },
+        "/wp/v2/widgets": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET",
+                "POST"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        },
+                        "sidebar": {
+                            "description": "The sidebar to return widgets for.",
+                            "type": "string",
+                            "required": false
+                        }
+                    }
+                },
+                {
+                    "methods": [
+                        "POST"
+                    ],
+                    "args": {
+                        "id": {
+                            "description": "Unique identifier for the widget.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "id_base": {
+                            "description": "The type of the widget. Corresponds to ID in widget-types endpoint.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "sidebar": {
+                            "default": "wp_inactive_widgets",
+                            "description": "The sidebar the widget belongs to.",
+                            "type": "string",
+                            "required": true
+                        },
+                        "instance": {
+                            "description": "Instance settings of the widget, if supported.",
+                            "type": "object",
+                            "properties": {
+                                "encoded": {
+                                    "description": "Base64 encoded representation of the instance settings.",
+                                    "type": "string",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                },
+                                "hash": {
+                                    "description": "Cryptographic hash of the instance settings.",
+                                    "type": "string",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                },
+                                "raw": {
+                                    "description": "Unencoded instance settings, if supported.",
+                                    "type": "object",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                }
+                            },
+                            "required": false
+                        },
+                        "form_data": {
+                            "description": "URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.",
+                            "type": "string",
+                            "required": false
+                        }
+                    }
+                }
+            ],
+            "_links": {
+                "self": [
+                    {
+                        "href": "http://example.org/index.php?rest_route=/wp/v2/widgets"
+                    }
+                ]
+            }
+        },
+        "/wp/v2/widgets/(?P<id>[\\w\\-]+)": {
+            "namespace": "wp/v2",
+            "methods": [
+                "GET",
+                "POST",
+                "PUT",
+                "PATCH",
+                "DELETE"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "GET"
+                    ],
+                    "args": {
+                        "context": {
+                            "description": "Scope under which the request is made; determines fields present in response.",
+                            "type": "string",
+                            "enum": [
+                                "view",
+                                "embed",
+                                "edit"
+                            ],
+                            "default": "view",
+                            "required": false
+                        }
+                    }
+                },
+                {
+                    "methods": [
+                        "POST",
+                        "PUT",
+                        "PATCH"
+                    ],
+                    "args": {
+                        "id": {
+                            "description": "Unique identifier for the widget.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "id_base": {
+                            "description": "The type of the widget. Corresponds to ID in widget-types endpoint.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "sidebar": {
+                            "description": "The sidebar the widget belongs to.",
+                            "type": "string",
+                            "required": false
+                        },
+                        "instance": {
+                            "description": "Instance settings of the widget, if supported.",
+                            "type": "object",
+                            "properties": {
+                                "encoded": {
+                                    "description": "Base64 encoded representation of the instance settings.",
+                                    "type": "string",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                },
+                                "hash": {
+                                    "description": "Cryptographic hash of the instance settings.",
+                                    "type": "string",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                },
+                                "raw": {
+                                    "description": "Unencoded instance settings, if supported.",
+                                    "type": "object",
+                                    "context": [
+                                        "view",
+                                        "edit",
+                                        "embed"
+                                    ]
+                                }
+                            },
+                            "required": false
+                        },
+                        "form_data": {
+                            "description": "URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.",
+                            "type": "string",
+                            "required": false
+                        }
+                    }
+                },
+                {
+                    "methods": [
+                        "DELETE"
+                    ],
+                    "args": {
+                        "force": {
+                            "description": "Whether to force removal of the widget, or move it to the inactive sidebar.",
+                            "type": "boolean",
+                            "required": false
+                        }
+                    }
+                }
+            ]
+        },
</ins><span class="cx" style="display: block; padding: 0 10px">         "/wp/v2/block-directory/search": {
</span><span class="cx" style="display: block; padding: 0 10px">             "namespace": "wp/v2",
</span><span class="cx" style="display: block; padding: 0 10px">             "methods": [
</span></span></pre>
</div>
</div>

</body>
</html>